前回の続き。
CakePHPは日本語ドキュメントがそこそこ充実しています。
たとえば公式にブログの作成方法が書いてあります。
http://www.cakephp.jp/doc/blog_tutorial.html
ただしCakePHPは1.2で大きな変更があったのでそのままでは動きません。
よくわからないまま作成してみます。
CakePHPをはじめ各フレームワークは、MVCというモデルに従って作成されています。
簡単に言うと、Controllerでリクエストを振り分けModelで処理を行いViewで表示する、という流れでプログラミングするという掟です。
CakePHPの場合、appフォルダ内にあるmodels、views、controllers各フォルダにそれぞれを記述していきます。
ということでとりあえずCakePHPのフォルダ構成の説明。
まずdocsはそのままドキュメントが入っていますが必要ないのでポイします。
cakeフォルダは基本的にシステムが使用するので変更はしません。
vendorsフォルダは外部ファイルを取り込むところで、他フレームワークのライブラリやPEARなどを使えたりするのですが、そんな高度なことはしないのでとりあえずパス。
appフォルダはユーザが作成したファイルを置いていく場所となります。
後述のヘルパーに機能を追加したい、といった場合はcakeフォルダ内の該当ファイルを直接いじるのではなく、appフォルダにコピーしてから変更することになります。
ではアプリを作る前に使用するDBの作成。
前回作成したPhpMyAdmin内のcakephpデータベースにテーブルを作成します。
cakePHPのお約束として、テーブル名をプログラム名と共通にすることが挙げられます(ただし複数形)
そうすることで特に何もしなくても自動的にプログラムとDBをリンクしてくれるのです。
凄すぎ。
テーブル作成は以下のSQL文をコピペするだけで終了。
見てのとおり公式そのままです。
CREATE TABLE posts ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, title VARCHAR(50), body TEXT, created DATETIME DEFAULT NULL, modified DATETIME DEFAULT NULL ); INSERT INTO posts (title,body,created) VALUES ('The title', 'This is the post body.', NOW()); INSERT INTO posts (title,body,created) VALUES ('A title once again', 'And the post body follows.', NOW()); INSERT INTO posts (title,body,created) VALUES ('Title strikes back', 'This is really exciting! Not.', NOW()); |
さくっとプログラム作成。
こちらも公式をコピペしただけ。
app/models/post.php
class Post extends AppModel{ var $name = 'Post'; } |
app/controllers/posts_controller.php
class PostsController extends AppController{ var $name = 'Posts'; function index(){ $this->set('posts', $this->Post->findAll()); } } |
app/views/posts/index.thtml
<h1>ブログの投稿</h1> <table> <tr> <th>Id <th>タイトル</th> <th>作成日</th> </tr> <?php foreach ($posts as $post): ?> <tr> <td><?php echo $post['Post']['id']; ?></td> <td> <?php echo $html->link($post['Post']['title'], "/posts/view/".$post['Post']['id']); ?> </td> <td><?php echo $post['Post']['created']; ?></td> </tr> <?php endforeach; ?> </table> |
foreach()~endforeachはforeach(){~}のエイリアスです。初めて知った。
しかし、postとpostsとPostとPostsはどう違うんだ?
早速ブラウザからアクセス。
http://localhost/src/php/fw/cake/posts/index
Error: The view for PostsController::index() was not found.
Error: Confirm you have created the file: C:\xampp\htdocs\src\php\fw\cake\app\views\posts\index.ctp
はて。
index.ctpが見つからないって言われても何でしょうそのファイル。
どうやらCakePHP1.2以降、Viewのファイル名がthtmlからctpに変更になったようです。
app/views/posts/index.htmlをindex.ctpをに変更すると今度はあっさり表示に成功。
動作の詳細を追ってみます。
まずアドレスのうち、http://localhost/src/php/fw/cake/までがCakePHPのパスです。
その後のposts/indexで、posts_controller.phpのindexメソッドにアクセスするということになります。
indexメソッドは変数'posts'にpost.phpで作成したPostクラスのfindAllメソッドの返り値を代入し、setメソッドでViewに送り込みます。
index.ctpは送り込まれた変数'posts'の内容を展開して表示しているだけです。
$html->linkで<a href~のタグを作成してくれるようです。
$html以外にも$formや$ajaxといった多くのクラスが定義されており、これらはヘルパーと呼ばれています。
cake\libs\view\helpersフォルダ内に置かれているので見てみるとよいかもしれません。
しかしModelが何をやっているのかよくわからないんだが。
よくわからないのはスルーして詳細表示をしてみることにします。
Controllerに新たにviewメソッドを追加します。
Viewにはview.ctpを新規作成します。
app/controllers/posts_controller.php
function view($id = 1){ $this->Post->id = $id; $this->set('post', $this->Post->read()); } |
app/views/posts/view.ctp
<h1><?php echo $post['Post']['title']?></h1> <p><small>作成日: <?php echo $post['Post']['created']?></small></p> <p><?php echo $post['Post']['body']?></p> |
http://localhost/src/php/fw/cake/posts/view/1とアクセスしてみると記事の詳細があっさり表示されました。
Post->idで読みたい記事のIDをセットした後、Post->readでその記事を取得できるようです。
相変わらずModelはよくわからないまま。
次に記事の追加削除機能を追加してみる。
Viewは新規作成し、ModelとControllerには追加します。
app/models/post.php
var $validate = array( 'title' => VALID_NOT_EMPTY, 'body' => VALID_NOT_EMPTY ); |
app/controllers/posts_controller.php
function add(){ if (!empty($this->data)){ if ($this->Post->save($this->data)){ $this->flash('投稿されました.','/posts'); } } } function delete($id){ $this->Post->del($id); $this->flash(' id: '.$id.' の投稿は削除されました。', '/posts'); } |
app/views/posts/add.tcp
<h1>投稿の追加</h1> <form method="post" action="<?php echo $html->url('/posts/add')?>"> <p> タイトル: <?php echo $html->input('Post/title', array('size' => '40'))?> <?php echo $html->tagErrorMsg('Post/title', 'タイトルは必ず入力してください。') ?> </p> <p> 本文: <?php echo $html->textarea('Post/body', array('rows'=>'10')) ?> <?php echo $html->tagErrorMsg('Post/body', '本文は必ず入力してください。') ?> </p> <p> <?php echo $html->submit('保存') ?> </p> </form> |
app/views/posts/index.ctpに新規記事作成、記事削除へのリンクを追加する。
<h1>ブログの投稿</h1> <p><?php echo $html->link('投稿の追加', '/posts/add'); ?></p> <table> <tr> <th>Id</th> <th colspan="2">タイトル</th> <th>作成日</th> </tr> <?php foreach ($posts as $post): ?> <tr> <td><?php echo $post['Post']['id']; ?></td> <td> <?php echo $html->link($post['Post']['title'], '/posts/view/'.$post['Post']['id']);?> </td> <td> <?php echo $html->link('削除',"/posts/delete/{$post['Post']['id']}",null,'本当に削除しますか');?> </td> <td><?php echo $post['Post']['created']; ?></td> </tr> <?php endforeach; ?> </table> |
見事にエラー。
コピペしているだけなのに何故?
CakePHP1.2からフォームの扱い方が変更になったようです。
$htmlではなく$formを使わなければならないらしい。
app/views/posts/add.ctp
<h1>投稿の追加</h1> <form method="post" action="<?php echo $html->url('/posts/add')?>"> <?php echo $form->create( 'post',array('action'=>'add')) ?> <p> タイトル: <?php echo $form->input('Post/title',array('size' => 40,'error'=>'タイトルは必ず入力してください')) ?> </p> <p> 本文: <?php echo $form->input('Post/body',array('type'=>'textarea','error'=>'本文は必ず入力してください')) ?> </p> <p> <?php echo $form->end( '登録' ) ?> </p> </form> |
成功。
バリデーションは行うわ画面遷移後のデータ保持も行うわDBへのデータ保存も行うわで一体どうなってんのこれ?ってかんじです。
ModelのVALID_NOT_EMPTYで空白のチェックを行っているようです。
Controllerのaddメソッドは、フォームが空でなければPost->saveでDBの相当するカラムに内容を記入し、flashで完了画面を表示しています。
Viewの$form->inputは<input />に相当するわけですが、引数の内容によって自動的にtextareaになったりcheckboxになったりselect~optionになったりするという恐ろしい機能を持っています。
しかし$validateは一体どのタイミングで呼ばれているんだ?
さて一通り投稿ができるようにはなったのですが、日本語を入力するとDBに文字化けして登録されてしまいました。
'encoding' => 'utf8'は設定しているのに何故?
とりあえずPhpMyAdminから挿入してみる、とその時点で既に文字化けしているではありませんか。
テーブル構造を確認。
SHOW CREATE TABLE posts CREATE TABLE `posts` ( `id` int(10) unsigned NOT NULL auto_increment, `title` varchar(50) default NULL, `body` text, `created` datetime default NULL, `modified` datetime default NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 |
入れた覚えのないDEFAULT CHARSET=latin1なる文字列が入っていますよ?
というわけでテーブルを作り直し。
CREATE TABLE `posts` ( `id` int(10) unsigned NOT NULL auto_increment, `title` varchar(50) default NULL, `body` text, `created` datetime default NULL, `modified` datetime default NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 |
今度こそみごとに日本語入力成功。
めでたしめでたし。
最後は記事編集です。
app/controllers/posts_controller.phpに追加。
function edit($id = null){ if (empty($this->data)){ $this->Post->id = $id; $this->data = $this->Post->read(); }else if ($this->Post->save($this->data['Post'])){ $this->flash('投稿を更新しました。','/posts'); } } |
app/views/posts/index.ctpの、"$html->link('削除')"の前の行に一行だけ挿入。
<?php echo $html->link('編集', '/posts/edit/'.$post['Post']['id']);?> |
app/views/posts/edit.ctpを作成。
<h1>投稿の編集</h1> <?php echo $form->create( 'post',array('action'=>'add')) ?> <?php echo $form->input('Post/id',array('type'=>'hidden')); ?> <p> タイトル: <?php echo $form->input('Post/title',array('size' => 40,'error'=>'タイトルは必ず入力してください')) ?> </p> <p> 本文: <?php echo $form->input('Post/body',array('type'=>'textarea','error'=>'本文は必ず入力してください')) ?> </p> <p> <?php echo $form->end('保存') ?> </p> </form> |
完成してしまいました。なんだこれ。
$form->input('Post/id',array('type'=>'hidden'))でhiddenフィールドを作成し、記事IDを持ちまわしています。
function editでは送信データがあれば保存し、なければ記事を拾ってきて表示しています。
さて、ここで存在しない記事IDを指定した場合、hiddenフィールドが何故か空白になっています。
そんなコード書いた記憶はないんだが一体何故に?
さて、最後にデフォルトページを変更しましょう。
http://localhost/src/php/fw/cake/
にアクセスするとCakePHPの情報が表示されてしまいます。
そこを飛ばして
http://localhost/src/php/fw/cake/posts/index
にアクセスさせてしまいましょう。
app\config\routes.phpの
Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); |
こちらは/、すなわちhttp://localhost/src/php/fw/cake/にアクセスしてきたらpagesコントローラを起動せよ、という意味です。
つまり、以下のように書き換えましょう。
Router::connect('/', array('controller' => 'posts', 'action' => 'index')); |
これで/にアクセスした際にもpostsコントローラのindexメソッドが実行されるようになりました。
以上で簡単なブログの作成が終了しました。
なんて簡単なのでしょう。
よかったよかった。
http://book.cakephp.org/ja/view/219/cakephp
に気が付いたのは全てが終わった後だったという。南無。