The Cookbook の CakePHP ブログチュートリアルを実践してみる(7)

10.1.9 記事の追加

Posts コントローラへの add() アクションの追加ですが、bake コマンドで既に生成されています。 チュートリアルのソースと bake で生成されたソースに若干の違いがあります。 bake で生成されたソースは $this->Session->setFlash() メソッドを利用して、遷移先の画面でメッセージを表示するようになっています。

10.1.10 データのバリデーション

Post モデルへのバリデーションの追加ですが、こちらも bake コマンドで既に生成されています。 チュートリアルでは minLength を指定し、必ず 1 文字以上の入力をさせようとしていますが、notempty を指定して、必須入力としたほうがわかりやすいのではと思います。

10.1.11 投稿記事の削除

Posts コントローラへの delete() アクションの追加ですが、こちらも bake コマンドで既に生成されています。 bake で生成されたソースは 引数が指定されていない場合、メッセージを表示するようになっています。

10.1.12 投稿記事の編集

Posts コントローラへの edit() アクションと edit ビューの追加ですが、こちらも bake コマンドで既に生成されています。 bake で生成されたソースは 引数、フォームデータが共に指定されていない場合、メッセージを表示するようになっています。

10.1.13 ルーティング(Routes)

以下の行をコメントアウトし、

Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));

以下の行を追加します。

Router::connect('/', array('controller' => 'posts', 'action' => 'index'));

これで、今までは http://localhost/cake_blog_tutorial/posts としてアクセスしていたところを http://localhost/cake_blog_tutorial/ でアクセスできるようになります。

10.1.14 まとめ

チュートリアルはこれで終わりです。
マニュアルの残り( http://book.cakephp.org/ja/ ) と APIリファレンス ( http://api.cakephp.org/classes ) を使って、自分のプロジェクトを始めよう! との事です。 訳文がおもしろいですね(笑)

The Cookbook の CakePHP ブログチュートリアルを実践してみる(6)

10.1.8 Postビューの作成

Posts コントローラに続いて Posts ビューを作成します。
ターミナルから以下のコマンドを実行します。

$ cd /Users/ryo/Sites/eclipse_workspace/cake_blog_tutorial/cake/console
$ ./cake bake


以下の画面が表示されます。

Welcome to CakePHP v1.2.0.7962 Console
---------------------------------------------------------------
App : app
Path: /Users/ryo/Sites/eclipse_workspace/cake_blog_tutorial/app
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[Q]uit
What would you like to Bake? (D/M/V/C/P/Q) 
> V

今回は View を生成したいので V を入力します。


以下の画面が表示されます。

--------------------------------------------------------------
Bake View
Path: /Users/ryo/Sites/eclipse_workspace/cake_blog_tutorial/app/views/
---------------------------------------------------------------
Possible Controllers based on your current database:
1. Posts
Enter a number from the list above, type in the name of another controller, or 'q' to exit  
[q] > 1

現在のデータベースから選択できるビューが一覧表示されます。
今回は posts テーブルから生成できる Posts ビューのみです。
1 を入力します。


以下の画面が表示されます。

Would you like to create some scaffolded views (index, add, view, edit) for this controller?
NOTE: Before doing so, you'll need to create your controller and model classes (including associated models). (y/n) 
[n] > y

index(一覧) , add(追加) , view(詳細) , edit(編集) といったビューを生成するか聞かれています。事前にコントローラとモデル(アソシエーションを設定している場合、設定した全てのモデル)を生成しておく必要があります。
y を入力します。


以下の画面が表示されます。

Would you like to create the methods for admin routing? (y/n) 
[n] > n

admin routing 用のメソッドを生成するか聞かれています。
n を入力します。


以下の画面が表示されます。

Creating file /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/app/views/posts/index.ctp
Wrote /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/app/views/posts/index.ctp

Creating file /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/app/views/posts/view.ctp
Wrote /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/app/views/posts/view.ctp

Creating file /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/app/views/posts/add.ctp
Wrote /Users/ryo/Sites/eclipse_workspace/cake_blog_tutorial/app/views/posts/add.ctp

Creating file /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/app/views/posts/edit.ctp
Wrote /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/app/views/posts/edit.ctp
---------------------------------------------------------------

View Scaffolding Complete.

---------------------------------------------------------------

もとの画面に戻ってくるので Q でBake をぬけます。


生成されたファイルは以下になります。

app/views/posts/index.ctp
<div class="posts index">
<h2><?php __('Posts');?></h2>
<p>
<?php
    echo $paginator->counter( array( 'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true) ) );
?></p>
<table cellpadding="0" cellspacing="0">
<tr>
    <th><?php echo $paginator->sort('id');?></th>
    <th><?php echo $paginator->sort('title');?></th>
    <th><?php echo $paginator->sort('body');?></th>
    <th><?php echo $paginator->sort('created');?></th>
    <th><?php echo $paginator->sort('modified');?></th>
    <th class="actions"><?php __('Actions');?></th>
</tr>
<?php
    $i = 0;
    foreach ($posts as $post) :
        $class = null;
        if ($i++ % 2 == 0) {
            $class = ' class="altrow"';
        }
?>
    <tr<?php echo $class;?>>
        <td>
            <?php echo $post['Post']['id']; ?>
        </td>
        <td>
            <?php echo $post['Post']['title']; ?>
        </td>
        <td>
            <?php echo $post['Post']['body']; ?>
        </td>
        <td>
            <?php echo $post['Post']['created']; ?>
	</td>
        <td>
            <?php echo $post['Post']['modified']; ?>
        </td>
        <td class="actions">
            <?php echo $html->link(__('View', true), array('action'=>'view', $post['Post']['id'])); ?>
            <?php echo $html->link(__('Edit', true), array('action'=>'edit', $post['Post']['id'])); ?>
            <?php echo $html->link(__('Delete', true), array('action'=>'delete', $post['Post']['id']), null, sprintf(__('Are you sure you want to delete # %s?', true), $post['Post']['id'])); ?>
        </td>
    </tr>
<?php endforeach; ?>
</table>
</div>
<div class="paging">
    <?php echo $paginator->prev('<< '.__('previous', true), array(), null, array('class'=>'disabled'));?> | 
    <?php echo $paginator->numbers();?>
    <?php echo $paginator->next(__('next', true).' >>', array(), null, array('class'=>'disabled'));?>
</div>
<div class="actions">
    <ul>
        <li><?php echo $html->link(__('New Post', true), array('action'=>'add')); ?></li>
    </ul>
</div>
app/views/posts/view.ctp
<div class="posts view">
<h2><?php  __('Post');?></h2>
    <dl><?php $i = 0; $class = ' class="altrow"';?>
        <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Id'); ?></dt>
        <dd<?php if ($i++ % 2 == 0) echo $class;?>>
            <?php echo $post['Post']['id']; ?>
            &nbsp;
        </dd>
        <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Title'); ?></dt>
        <dd<?php if ($i++ % 2 == 0) echo $class;?>>
            <?php echo $post['Post']['title']; ?>
            &nbsp;
        </dd>
        <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Body'); ?></dt>
        <dd<?php if ($i++ % 2 == 0) echo $class;?>>
            <?php echo $post['Post']['body']; ?>
            &nbsp;
        </dd>
        <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Created'); ?></dt>
        <dd<?php if ($i++ % 2 == 0) echo $class;?>>
            <?php echo $post['Post']['created']; ?>
            &nbsp;
        </dd>
        <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Modified'); ?></dt>
        <dd<?php if ($i++ % 2 == 0) echo $class;?>>
            <?php echo $post['Post']['modified']; ?>
            &nbsp;
        </dd>
    </dl>
</div>
<div class="actions">
    <ul>
        <li><?php echo $html->link(__('Edit Post', true), array('action'=>'edit', $post['Post']['id'])); ?> </li>
        <li><?php echo $html->link(__('Delete Post', true), array('action'=>'delete', $post['Post']['id']), null, sprintf(__('Are you sure you want to delete # %s?', true), $post['Post']['id'])); ?> </li>
        <li><?php echo $html->link(__('List Posts', true), array('action'=>'index')); ?> </li>
        <li><?php echo $html->link(__('New Post', true), array('action'=>'add')); ?> </li>
    </ul>
</div>
app/views/posts/add.ctp
<div class="posts form">
<?php echo $form->create('Post');?>
    <fieldset>
        <legend><?php __('Add Post');?></legend>
    <?php
        echo $form->input('title');
        echo $form->input('body');
    ?>
    </fieldset>
<?php echo $form->end('Submit');?>
</div>
<div class="actions">
    <ul>
        <li><?php echo $html->link(__('List Posts', true), array('action'=>'index'));?></li>
    </ul>
</div>
app/views/posts/edit.ctp
<div class="posts form">
<?php echo $form->create('Post');?>
    <fieldset>
        <legend><?php __('Edit Post');?></legend>
    <?php
        echo $form->input('id');
        echo $form->input('title');
        echo $form->input('body');
    ?>
    </fieldset>
<?php echo $form->end('Submit');?>
</div>
<div class="actions">
    <ul>
        <li><?php echo $html->link(__('Delete', true), array('action'=>'delete', $form->value('Post.id')), null, sprintf(__('Are you sure you want to delete # %s?', true), $form->value('Post.id'))); ?></li>
    <li><?php echo $html->link(__('List Posts', true), array('action'=>'index'));?></li>
    </ul>
</div>

The Cookbook の CakePHP ブログチュートリアルを実践してみる(5)

10.1.7 Postsコントローラの作成

Post モデルに続いて Posts コントローラを作成します。
ターミナルから以下のコマンドを実行します。

$ cd /Users/ユーザ名/Sites/eclipse_workspace/cake_blog_tutorial/cake/console
$ ./cake bake


以下の画面が表示されます。

Welcome to CakePHP v1.2.0.7962 Console
---------------------------------------------------------------
App : app
Path: /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/app
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[Q]uit
What would you like to Bake? (D/M/V/C/P/Q) 
> C

今回はコントローラを生成したいので C を入力します。


以下の画面が表示されます。

---------------------------------------------------------------
Bake Controller
Path: /Users/ryo/Sites/eclipse_workspace/cake_blog_tutorial/app/controllers/
---------------------------------------------------------------
Possible Controllers based on your current database:
1. Posts
Enter a number from the list above, type in the name of another controller, or 'q' to exit  
[q] > 

現在のデータベースから選択できるコントローラが一覧表示されます。
今回は posts テーブルから生成できる Posts コントローラのみです。
1 を入力します。


以下の画面が表示されます。

---------------------------------------------------------------
Baking PostsController
---------------------------------------------------------------
Would you like to build your controller interactively? (y/n) 
[y] > y

対話形式でコントローラを作成するかと聞かれています。
y を入力します。


以下の画面が表示されます。

Would you like to use scaffolding? (y/n) 
[n] > n

scaffolding 機能を利用するかと聞かれています。
n を入力します。


以下の画面が表示されます。

Would you like to include some basic class methods (index(), add(), view(), edit())? (y/n) 
[n] > y

index(一覧) , add(追加) , view(詳細) , edit(編集) といった基本的なメソッドを生成するか聞かれています。
y を入力します。


以下の画面が表示されます。

Would you like to create the methods for admin routing? (y/n) 
[n] > n

admin routing 用のメソッドを生成するか聞かれています。
n を入力します。


以下の画面が表示されます。

Would you like this controller to use other helpers besides HtmlHelper and FormHelper? (y/n) 
[n] > n

HtmlHelper と FormHelper 以外のヘルパーを利用するか聞かれています。
n を入力します。


以下の画面が表示されます。

Would you like this controller to use any components? (y/n) 
[n] > n

何かコンポーネントを利用するか聞かれています。
n を入力します。


以下の画面が表示されます。

Would you like to use Sessions? (y/n) 
[y] > y

Session を利用するか聞かれています。
y を入力します。


以下の画面が表示されます。

---------------------------------------------------------------
The following controller will be created:
---------------------------------------------------------------
Controller Name:  Posts

Notice: Undefined variable: helpers in /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/cake/console/libs/tasks/controller.php on line 195

Notice: Undefined variable: components in /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/cake/console/libs/tasks/controller.php on line 207
---------------------------------------------------------------
Look okay? (y/n) 
[y] > y

今まで設定したきた項目の確認です。
Notice は無視して構いません。
問題は無いので y を入力します。


以下の画面が表示されます。

Notice: Undefined variable: helpers in /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/cake/console/libs/tasks/controller.php on line 222

Notice: Undefined variable: components in /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/cake/console/libs/tasks/controller.php on line 222

Notice: Undefined variable: uses in /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/cake/console/libs/tasks/controller.php on line 222

Creating file /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/app/controllers/posts_controller.php
Wrote /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/app/controllers/posts_controller.php
Cake test suite not installed.  Do you want to bake unit test files anyway? (y/n) 
[y] > n

ユニットテスト用のファイルを生成するかを聞いかれています。
今回は ユニットテスト用のファイルは生成しません。
n を入力します。
Notice は無視して構いません。


もとの画面に戻ってくるので Q でBake をぬけます
生成されたファイルは以下になります。

<?php
class PostsController extends AppController {

	var $name = 'Posts';
	var $helpers = array('Html', 'Form');

	function index() {
		$this->Post->recursive = 0;
		$this->set('posts', $this->paginate());
	}

	function view($id = null) {
		if (!$id) {
			$this->Session->setFlash(__('Invalid Post.', true));
			$this->redirect(array('action'=>'index'));
		}
		$this->set('post', $this->Post->read(null, $id));
	}

	function add() {
		if (!empty($this->data)) {
			$this->Post->create();
			if ($this->Post->save($this->data)) {
				$this->Session->setFlash(__('The Post has been saved', true));
				$this->redirect(array('action'=>'index'));
			} else {
				$this->Session->setFlash(__('The Post could not be saved. Please, try again.', true));
			}
		}
	}

	function edit($id = null) {
		if (!$id && empty($this->data)) {
			$this->Session->setFlash(__('Invalid Post', true));
			$this->redirect(array('action'=>'index'));
		}
		if (!empty($this->data)) {
			if ($this->Post->save($this->data)) {
				$this->Session->setFlash(__('The Post has been saved', true));
				$this->redirect(array('action'=>'index'));
			} else {
				$this->Session->setFlash(__('The Post could not be saved. Please, try again.', true));
			}
		}
		if (empty($this->data)) {
			$this->data = $this->Post->read(null, $id);
		}
	}

	function delete($id = null) {
		if (!$id) {
			$this->Session->setFlash(__('Invalid id for Post', true));
			$this->redirect(array('action'=>'index'));
		}
		if ($this->Post->del($id)) {
			$this->Session->setFlash(__('Post deleted', true));
			$this->redirect(array('action'=>'index'));
		}
	}

}
?>

The Cookbook の CakePHP ブログチュートリアルを実践してみる(4)

10.1.6 Postモデルの作成

Bake を利用してコードの自動生成をしてみます。 Bake の本体は cake/console 内にあります。
実行権限が無いと Bake を実行できないので、chmod コマンドで実行権限をつけてから Bake を実行します。
ターミナルから以下のコマンドを入力します。

$ cd /Users/ユーザ名/Sites/eclipse_workspace/cake_blog_tutorial/cake/console
$ chmod 755 cake
$ ./cake bake


以下の画面が表示されます。

Welcome to CakePHP v1.2.0.7962 Console
---------------------------------------------------------------
App : app
Path: /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/app
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[Q]uit
What would you like to Bake? (D/M/V/C/P/Q) 
> M

Database Configuration はデータベースの接続情報の設定。 Model はモデルクラス。 View はビューファイル。 Controller はコントローラークラス。 Project はプロジェクトを、それぞれ生成します。 Quit は Bake を終了します。

今回はモデルクラスを生成したいので M を入力します。


以下の画面が表示されます。

---------------------------------------------------------------
Bake Model
Path: /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/app/models/
---------------------------------------------------------------
Use Database Config: (default/test) 
[default] > default

database.php に設定したデータベースの接続情報のうち、どれを利用するかを入力します。
今回は default を利用するので default を入力 もしくは 何も入力しないで return キーを押します。


以下の画面が表示されます。

Possible Models based on your current database:
1. Post
Enter a number from the list above, type in the name of another model, or 'q' to exit  
[q] > 1

現在のデータベースから選択できるモデルが一覧表示されます。
今回は posts テーブルから生成できる Post モデルのみです。
1 を入力します。


以下の画面が表示されます。

Would you like to supply validation criteria for the fields in your model? (y/n) 
[y] > y

バリデーションの定義をするかを聞かれています。
バリデーションの設定をするために y を入力 もしくは 何も入力しないで return キーを押します。


以下の画面が表示されます。

Field: id
Type: integer
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1 - alphaNumeric
2 - between
3 - blank
4 - boolean
5 - cc
6 - comparison
7 - custom
8 - date
9 - decimal
10 - email
11 - equalTo
12 - extension
13 - file
14 - inList
15 - ip
16 - maxLength
17 - minLength
18 - money
19 - multiple
20 - notEmpty
21 - numeric
22 - phone
23 - postal
24 - range
25 - ssn
26 - time
27 - url
28 - userDefined
29 - Do not do any validation on this field.
... or enter in a valid regex validation string.
  
[29] > 29

id フィールドに対してどのバリデーションを適用するかを聞かれています。
id フィールドにはバリデーションを設定したくないので 29 を入力します。


残りのフィールドに対してもバリデーションの設定がつづくので、title と body には 20 を入力し、必須入力にします。 created と modified には id 同様、バリデーションの必要は無いので 29 を入力します。


以下の画面が表示されます。

Would you like to define model associations (hasMany, hasOne, belongsTo, etc.)? (y/n) 
[y] > n

モデルのアソシエーションを定義するか聞かれています。
今回は 1 テーブルしかないので、アソシエーションの必要はありません。
n を入力します。


以下の画面が表示されます。

---------------------------------------------------------------
The following Model will be created:
---------------------------------------------------------------
Name:       Post
Validation: Array
(
    [title] => notempty
    [body] => notempty
)

Associations:
---------------------------------------------------------------
Look okay? (y/n) 
[y] > y

今まで設定したきた項目の確認です。
問題は無いので y を入力します。


以下の画面が表示されます。

Baking model class for Post...

Creating file /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/app/models/post.php
Wrote /Users/*****/Sites/eclipse_workspace/cake_blog_tutorial/app/models/post.php
Cake test suite not installed.  Do you want to bake unit test files anyway? (y/n) 
[y] > n

ユニットテスト用のファイルを生成するかを聞いかれています。
今回は ユニットテスト用のファイルは生成しません。
n を入力します。


もとの画面に戻ってくるので Q でBake をぬけます


生成されたファイルは以下になります。

app/models/post.php
<?php
class Post extends AppModel {

	var $name = 'Post';
	var $validate = array(
		'title' => array('notempty'),
		'body' => array('notempty')
	);

}
?>

CakePHP : モデルとデータベースの規約

テーブルの命名規約
テーブル名
複数形でアンダースコア記法。 persons , big_persons, really_big_persons など
主キー
id という名前の整数型で、オートインクリメントを指定。複合主キーはサポートされていない
外部キー
テーブル名の単数形_id という名前。 person_id , big_person_id , really_big_person_id など
カラム名 ( name もしくは title )
フィールドのラベルとして優先的に利用される
カラム名 ( created )
レコードが一番初めに追加されたときに、現在日時がセットされる。datetime 型を指定
カラム名 ( modified もしくは updated )
すでに存在するレコードが保存されたときに、現在日時で更新される。datetime 型を指定
モデルクラスの命名規約

単数形でキャメル記法。 Person、BigPerson、ReallyBigPerson など

The Cookbook の CakePHP ブログチュートリアルを実践してみる(3)

[ 10.1.4 追加の設定 ]

チュートリアルにある通り、ハッシュの生成に用いられる文字列を変更します。

/app/config/core.php
Configure::write('Security.salt', 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi');

↓ ↓ ↓

Configure::write('Security.salt', '任意の文字列');


/app/tmp ディレクトリのパーミッション変更はインストール時の手順で既に済ませてしまったので割愛します。
チュートリアルでは、パーミッション変更では無く、web サーバのユーザに所有権を変更するのがベストと書いてあります。運用段階ではそうすべきだと思います。

[ 10.1.5 mod_rewriteについて ]

mod_rewrite の設定についてと、mod_rewrite を利用しない ( できない ) 場合についての記述があります。

mod_rewrite の設定はインストール時の手順で既に済ませてしまったので割愛します。


http://localhost/cake_blog_tutorial/ へアクセスしてみます。
設定に誤りがなければ CakePHP の初期画面が表示され、warningや notice 等の表示が無くなっているはずです。
また、以下の文言が表示されていれば、データベースへの接続が正常に行われています。

Your database configuration file is present.

Cake is able to connect to the database.

The Cookbook の CakePHP ブログチュートリアルを実践してみる(2)

[ 10.1.3 Cakeのデータベース設定 ]

(1) で作成したデータベースへ、同じく (1) で作成したユーザで接続します。
データベース設定ファイル database.php を /app/config/database.php.default をコピーして作成します。
複数の接続情報を配列で記述する事ができますが、デフォルトで使用されるのは $default 配列なので、この配列の値を以下のように修正します。

/app/config/database.php
var $default = array(
    'driver' => 'mysql',
    'persistent' => false,
    'host' => 'localhost',
    'login' => 'cake_blog',
    'password' => '12345',
    'database' => 'cake_blog_tutorial',
    'prefix' => '',
    'port' => '',
    'encoding' => 'utf8',
    'schema' => '',
);
設定用配列の各項目の詳細
driver
データベースのドライバ名。mysql, postgres, sqlite, pear-drivername, adodb-drivername, mssql, oracle, odbc など
persistent
持続的接続(persistent connection)を使うかどうかを設定
host
データベースサーバのホスト名。(またはIPアドレス
login
アカウントのユーザ名
password
アカウントのパスワード
database
この接続が利用するデータベース名
prefix (オプション)
データベース内のすべてのテーブルの頭に付ける接頭辞(prefix)。もしテーブルに接頭辞が付いていないのであれば指定しない
port (オプション)
サーバに接続するためのTCPポート、またはUnix socket
encoding
サーバにSQLステートメントを送信する際に使用するキャラクターセット
schema
PostgreSQLデータベースの設定時に、どのスキーマを使うかを指定