前回はデータベーススキーマに関するコンソール操作を説明したんだけど、なかなか興味深い内容だったと思う。
今回はいわゆるBake(ベイク)を使ってみる予定。
BakeというのはCakePHPというケーキを美味しく焼きあげるために用意された、いわばCakePHPのユーザランドファイルの自動生成ライブラリだ。簡単な設定で面倒な各種ファイルの準備をしてくれる。
作業に先立ち、今回のスキーマを書いておくので、コピペする人は参考にされたし。
app/Config/Schema/schema.php
class AppSchema extends CakeSchema { public function before($event = array()) { return true; } public function after($event = array()) { } public $users = array( 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'length' => 5, 'key' => 'primary', 'collate' => NULL, 'comment' => ''), 'username' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 64, 'key' => 'index', 'collate' => 'utf8_general_ci', 'comment' => '', 'charset' => 'utf8'), 'password' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'collate' => 'utf8_general_ci', 'comment' => '', 'charset' => 'utf8'), 'is_enabled' => array('type' => 'boolean', 'null' => false, 'default' => NULL, 'collate' => NULL, 'comment' => ''), 'is_deleted' => array('type' => 'boolean', 'null' => false, 'default' => NULL, 'collate' => NULL, 'comment' => ''), 'created' => array('type' => 'datetime', 'null' => false, 'default' => NULL, 'collate' => NULL, 'comment' => ''), 'modefied' => array('type' => 'datetime', 'null' => false, 'default' => NULL, 'collate' => NULL, 'comment' => ''), 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'users_idx' => array('column' => array('username', 'password', 'is_enabled', 'is_deleted', 'created', 'modefied'), 'unique' => 0)), 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB') ); public $profiles = array( 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => 5, 'key' => 'primary', 'collate' => null, 'comment' => ''), 'user_id' => array( 'type' => 'integer', 'null' => false, 'default' => null, 'length' => 5, 'key' => 'index', 'collate' => null, 'comment' => '' ), 'name' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 32, 'key' => 'index', 'collate' => 'utf8_general_ci', 'comment' => '表示名', 'charset' => 'utf8'), 'created' => array('type' => 'datetime', 'null' => false, 'default' => NULL, 'collate' => NULL, 'comment' => ''), 'modified' => array('type' => 'datetime', 'null' => false, 'default' => NULL, 'collate' => NULL, 'comment' => ''), 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'profile_idx' => array('column' => array('user_id', 'name', 'created', 'modified'), 'unique' => 1)), 'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB') ); }このファイルを作成後、コンソールでスキーマを自動生成。
$ ./cake schema -app ../../app create
このままでもいいんだけど、int型がただの整数になってしまっているので、可能であれば符号なしの正の数にしておきたい。
MySQL WorkbenchやphpMyAdminなどのGUIツールでunsignedに変更しても良いし、以下のSQLを実行するなどで対応しておこう。
ALTER TABLE `profiles` CHANGE `id` `id` INT( 5 ) UNSIGNED NOT NULL AUTO_INCREMENT ALTER TABLE `profiles` CHANGE `user_id` `user_id` INT( 5 ) UNSIGNED NOT NULL ALTER TABLE `users` CHANGE `id` `id` INT( 5 ) UNSIGNED NOT NULL AUTO_INCREMENT ALTER TABLE `users` CHANGE `is_enabled` `is_enabled` TINYINT( 1 ) UNSIGNED NOT NULL COMMENT '使用フラグ', CHANGE `is_deleted` `is_deleted` TINYINT( 1 ) UNSIGNED NOT NULL COMMENT '削除フラグ'
出来ればschemaコマンド側でこの対応をしてもらうと助かるんだけどね。しばらく掛かりそう。
■モデルを作ってみよう
さて、すでにデータベーススキーマが生成されているので、Bakeがモデルを認識してくれるはず。
一旦Bakeを起動して確かめてみよう。
$ ./cake bake -app ../../appこのコマンドがBakeの基本だ。正しく入力すると、以下のようなメニューが表示される。
Welcome to CakePHP v2.0.5 Console --------------------------------------------------------------- App : app Path: /cakephp2.0/htdocs/app/Console/../../app/ --------------------------------------------------------------- Interactive Bake Shell --------------------------------------------------------------- [D]atabase Configuration [M]odel [V]iew [C]ontroller [P]roject [F]ixture [T]est case [Q]uit What would you like to Bake? (D/M/V/C/P/F/T/Q) >上から順に説明すると、
- [D]atabase Configuration
- app/Config/database.phpの自動生成
- [M]odel
- app/Model内にモデルファイル自動生成
- [V]iew
- app/View内にビューファイル自動生成
- [C]ontroller
- app/Controller内にコントローラファイル自動生成
- [P]roject
- appと同じフォルダ構造(プロジェクト)を自動生成
- [F]ixture
- テストで使うフィクスチャ自動生成
- [T]est case
- テストケース自動生成
- [Q]uit
- Bakeの終了
ちなみにコマンドで直接指定することも可能だ。
その場合、上記には表示されてないが、プラグインも可能となっている。
以下がそのコマンドになる。
$ cake bake <オプション>オプションは以下が使用可能。
- db_config
- model
- view
- controller
- project
- fixture
- test
- plugin <プラグイン名>
- all
今回はすでにデータベーススキーマが存在しているのが前提なので、モデルから作ってみよう。「m」もしくは「M」をタイプしてEnterする。
--------------------------------------------------------------- Interactive Bake Shell --------------------------------------------------------------- [D]atabase Configuration [M]odel [V]iew [C]ontroller [P]roject [F]ixture [T]est case [Q]uit What would you like to Bake? (D/M/V/C/P/F/T/Q) > m ◀ モデルを自動生成させるので「m」もしくは「M」 --------------------------------------------------------------- Bake Model Path: /cakephp2.0/htdocs/app/Console/../../app/Model/ --------------------------------------------------------------- Use Database Config: (default/test) [default] > ◀ database.phpの$defaultを使用するので「Enter」 Possible Models based on your current database: 1. Profile 2. User Enter a number from the list above, type in the name of another model, or 'q' to exit [q] > 1 ◀ Profileモデルを先に作るので「1」 Would you like to supply validation criteria for the fields in your model? (y/n) [y] > n ◀ バリデーションはとりあえず後にするので「n」 Would you like to define model associations (hasMany, hasOne, belongsTo, etc.)? (y/n) [y] > y ◀ Userモデルとアソシエーションするので「y」もしくは「Enter」 One moment while the associations are detected. --------------------------------------------------------------- Please confirm the following associations: --------------------------------------------------------------- Profile belongsTo User? (y/n) [y] > y ◀ Profile belongs to Userなので「y」もしくは「Enter」 Would you like to define some additional model associations? (y/n) [n] > n ◀ 他にアソシエーション張るモデルは無いので「n」もしくは「Enter」 --------------------------------------------------------------- The following Model will be created: --------------------------------------------------------------- Name: Profile DB Table: `profiles` Associations: Profile belongsTo User --------------------------------------------------------------- Look okay? (y/n) [y] > y ◀ これで良いか?と聞かれるので「y」もしくは「Enter」これだけの問答で、以下のソースが生成されるんだ。
app/Model/Profile.php
App::uses('AppModel', 'Model'); /** * Profile Model * * @property User $User */ class Profile extends AppModel { /** * Display field * * @var string */ public $displayField = 'name'; //The Associations below have been created with all possible keys, those that are not needed can be removed /** * belongsTo associations * * @var array */ public $belongsTo = array( 'User' => array( 'className' => 'User', 'foreignKey' => 'user_id', 'conditions' => '', 'fields' => '', 'order' => '' ) ); }
同じように、Userモデルも作ってみよう。
--------------------------------------------------------------- Bake Model Path: /cakephp2.0/htdocs/app/Console/../../app/Model/ --------------------------------------------------------------- Possible Models based on your current database: 1. Profile 2. User Enter a number from the list above, type in the name of another model, or 'q' to exit [q] > 2 ◀ Userモデルを作るので「2」 A displayField could not be automatically detected would you like to choose one? (y/n) > n ◀ $displayFieldが自動検出できなかったが使わないので「n」 Would you like to supply validation criteria for the fields in your model? (y/n) [y] > n ◀ バリデーションはとりあえず後にするので「n」 Would you like to define model associations (hasMany, hasOne, belongsTo, etc.)? (y/n) [y] > y ◀ Profileとアソシエーション張るので「y」もしくは「Enter」 One moment while the associations are detected. --------------------------------------------------------------- Please confirm the following associations: --------------------------------------------------------------- User hasMany Profile? (y/n) [y] > n ◀ hasManyではないので「n」 User hasOne Profile? (y/n) [y] > y ◀ hasOneなので「y」もしくは「Enter」 Would you like to define some additional model associations? (y/n) [n] > n ◀ 他にアソシエーション張るモデルは無いので「n」もしくは「Enter」 --------------------------------------------------------------- The following Model will be created: --------------------------------------------------------------- Name: User DB Table: `users` Associations: User hasOne Profile --------------------------------------------------------------- Look okay? (y/n) [y] > y ◀ これで良いか?と聞かれるので「y」もしくは「Enter」これでProfile.phpと同じように、以下のソースが自動生成される。
app/Model/User.php
App::uses('AppModel', 'Model'); /** * User Model * * @property Profile $Profile */ class User extends AppModel { //The Associations below have been created with all possible keys, those that are not needed can be removed /** * hasOne associations * * @var array */ public $hasOne = array( 'Profile' => array( 'className' => 'Profile', 'foreignKey' => 'user_id', 'conditions' => '', 'fields' => '', 'order' => '' ) ); }もうナイス過ぎて下痢しそうだ。
モデルはこれで一旦完了とする。
■コントローラを作ってみよう
さて、モデルファイルを自動生成したように、今度はコントローラを作ってみようじゃないか。
どういった内容のアクションを作るのかというと、基本的な以下の5つになる。
- Userの一覧
- Userの詳細
- Userの追加
- Userの編集
- Userの削除
という訳で早速行ってみよう。
Welcome to CakePHP v2.0.5 Console --------------------------------------------------------------- App : app Path: /cakephp2.0/htdocs/app/Console/../../app/ --------------------------------------------------------------- Interactive Bake Shell --------------------------------------------------------------- [D]atabase Configuration [M]odel [V]iew [C]ontroller [P]roject [F]ixture [T]est case [Q]uit What would you like to Bake? (D/M/V/C/P/F/T/Q) > c ◀ Controllerを作るので「c」もしくは「C」 --------------------------------------------------------------- Bake Controller Path: /cakephp2.0/htdocs/app/Console/../../app/Controller/ --------------------------------------------------------------- Use Database Config: (default/test) [default] > ◀ database.phpの$defaultを使用するので「Enter」 Possible Controllers based on your current database: 1. Profiles 2. Users Enter a number from the list above, type in the name of another controller, or 'q' to exit [q] > 2 ◀ Usersコントローラを作成するので「2」 --------------------------------------------------------------- Baking UsersController --------------------------------------------------------------- Would you like to build your controller interactively? (y/n) [y] > y ◀ 対話式に構築していくので「y」もしくは「Enter」 Would you like to use dynamic scaffolding? (y/n) [n] > n ◀ 動的スカフォルドしないので「n」もしくは「Enter」 Would you like to create some basic class methods (index(), add(), view(), edit())? (y/n) [n] > y ◀ 一覧、追加、詳細、編集アクションを自動生成するので「y」 Would you like to create the basic class methods for admin routing? (y/n) [n] > n ◀ 管理用アプリは今は作らないので「n」もしくは「Enter」 Would you like this controller to use other helpers besides HtmlHelper and FormHelper? (y/n) [n] > n ◀ 今のところHTMLヘルパー、Formヘルパー以外使わないので「n」もしくは「Enter」 Would you like this controller to use any components? (y/n) [n] > n ◀ コンポーネントはまだ使用しないので「n」もしくは「Enter」 Would you like to use Session flash messages? (y/n) [y] > y ◀ セッションフラッシュメッセージを使うので「y」もしくは「Enter」 --------------------------------------------------------------- The following controller will be created: --------------------------------------------------------------- Controller Name: Users --------------------------------------------------------------- Look okay? (y/n) [y] > y ◀ これでよろしいかと聞かれるので「y」もしくは「Enter」これで以下のようなソースが自動生成される。
app/Controller/UsersController.php
App::uses('AppController', 'Controller'); /** * Users Controller * * @property User $User */ class UsersController extends AppController { /** * index method * * @return void */ public function index() { $this->User->recursive = 0; $this->set('users', $this->paginate()); } /** * view method * * @param string $id * @return void */ public function view($id = null) { $this->User->id = $id; if (!$this->User->exists()) { throw new NotFoundException(__('Invalid user')); } $this->set('user', $this->User->read(null, $id)); } /** * add method * * @return void */ public function add() { if ($this->request->is('post')) { $this->User->create(); if ($this->User->save($this->request->data)) { $this->Session->setFlash(__('The user has been saved')); $this->redirect(array('action' => 'index')); } else { $this->Session->setFlash(__('The user could not be saved. Please, try again.')); } } } /** * edit method * * @param string $id * @return void */ public function edit($id = null) { $this->User->id = $id; if (!$this->User->exists()) { throw new NotFoundException(__('Invalid user')); } if ($this->request->is('post') || $this->request->is('put')) { if ($this->User->save($this->request->data)) { $this->Session->setFlash(__('The user has been saved')); $this->redirect(array('action' => 'index')); } else { $this->Session->setFlash(__('The user could not be saved. Please, try again.')); } } else { $this->request->data = $this->User->read(null, $id); } } /** * delete method * * @param string $id * @return void */ public function delete($id = null) { if (!$this->request->is('post')) { throw new MethodNotAllowedException(); } $this->User->id = $id; if (!$this->User->exists()) { throw new NotFoundException(__('Invalid user')); } if ($this->User->delete()) { $this->Session->setFlash(__('User deleted')); $this->redirect(array('action' => 'index')); } $this->Session->setFlash(__('User was not deleted')); $this->redirect(array('action' => 'index')); } }NetBeansのナビゲータでコントローラを見てみると、このような感じになっている。
簡単にアクションを説明してみよう。
indexアクション
Userモデルから全データを引っ張ってきて、users変数に代入している。
viewアクション
$idをidに持つUserモデルから1つデータを引っ張ってくる。
$idの指定がなかった場合はNotFoundException例外を投げる。
addアクション
ポストされた場合、入力されたデータをUserモデルに保存する。
保存が失敗したらエラーメッセージをフラッシュする。
editアクション
$idの指定がなかった場合はNotFoundExceptionという例外を投げる。
$idをidにもつUserモデルから1つデータを引っ張ってくる。
ポストされた場合、入力されたデータをUserモデルに保存する。
ポストされてなければ引っ張ってきたデータをリクエストデータに代入
deleteアクション
リクエストがpost形式出なかった場合、MethodNotAllowedException例外を投げる。
Userモデルのidプロパティをセットする。
該当するUserモデルがなければNotFoundException例外を投げる。
該当するUserモデルを削除し、メッセージをフラッシュする。
削除に失敗したらエラーメッセージをフラッシュする。
だいたいこんな感じだ。
同じようにして、ProfileControllerも作っておいてくだされ。
このBakeで自動生成されたソースはかなり参考になるので、最低一度はBakeでコントローラを作成しておくことをすすめるよ。
■ビューをつくってみよう
さて、モデル、コントローラとできたら、今度は具体的にブラウザで表示するビューの出番だ。これもBakeで自動生成させてしまおう。
Welcome to CakePHP v2.0.5 Console --------------------------------------------------------------- App : app Path: /cakephp2.0/htdocs/app/Console/../../app/ --------------------------------------------------------------- Interactive Bake Shell --------------------------------------------------------------- [D]atabase Configuration [M]odel [V]iew [C]ontroller [P]roject [F]ixture [T]est case [Q]uit What would you like to Bake? (D/M/V/C/P/F/T/Q) > v ◀ ビューファイルを自動生成するので「v」もしくは「V」 --------------------------------------------------------------- Bake View Path: /home/develop/cake2.torhamzedd.com/htdocs/app/Console/../../app/View/ --------------------------------------------------------------- Use Database Config: (default/test) [default] > ◀ database.phpの$defaultを使用するので「Enter」 Possible Controllers based on your current database: 1. Profiles 2. Users Enter a number from the list above, type in the name of another controller, or 'q' to exit [q] > 2 ◀ Usersコントローラがベースになるので「2」 Would you like bake to build your views interactively? Warning: Choosing no will overwrite Profiles views if it exist. (y/n) [n] > n ◀ まだProfile用のビューは作らないので「n」もしくは「Enter」これで、以下のファイルが生成される。
- app/View/Users/add.ctp
- app/View/Users/edit.ctp
- app/View/Users/index.ctp
- app/View/Users/view.ctp
Usersと同じく、Profilesのビューも作っておいてくだされ。
これでひと通り、最低限ブラウザで確認できるまで自動生成させることができた。
■ブラウザで見てみよう
アクセスするURLは/usersになる。
自動的にUsersControllerのindexアクションが実行され、Users/index.ctpをテンプレートとして表示してくれるはずだ。
これまたなんと凝った画面だこと。
CakePHP1.2の時代に比べると、恐ろしく発展を遂げているではないか。
画面左側にはナビゲーションまで付いている。
- New User
- List Profile
- New Profile
試しに「New User」でユーザを追加してみよう。
適当にデータを入れて、「Submit」ボタンをクリック。
正直言うと、赤はやめて欲しいんだけど、どうやら問題なくUserモデルにユーザが追加できたようだ。
セッションフラッシュでメッセージを表示させているようなので、F5キーなどでリロードしたら、この赤い忌々しいメッセージは消える。
さて、次はプロフィールを追加だ。画面左の「New Profile」をクリック。
俺の嫁の名前を入れて、「Submit」ボタンをクリック。
正直言うと、赤はやめて欲しいんだけど、どうやら問題なくProfileモデルにプロフィールが追加できたようだ。
セッションフラッシュでメッセージを表示させているようなので、F5キーなどでリロードしたら、この赤い不安なメッセージは消える。
「List User」で一旦一覧へ戻り、今追加したユーザの「View」ボタンを押してみよう。
このように、俺の嫁の詳細を確認することができる。
パスワードがモロ出しなのはサービスだ。というのは嘘で、Authコンポーネントを使えば勝手に暗号化してくれるので、安心されたし。
ちなみに「Delete」する場合、即座には消さない。一旦アラートダイアログが表示され、そこで本当に消すのかどうかを再度確認することができるので、結構安心。
OSやブラウザによっては、予め「OK」にフォーカスが当たっていることがあるので、くれぐれもEnterやスペースキーの連打には要注意だ。
■終わり
Bakeは本当に開発を助けてくれると思う。
Webアプリの設計段階でまず、モデルを決めてしまおう。そうすればスキーマが決まり、あとはBakeで焼くだけだ。
実際には、モデルにはビヘイビア(Behavior)、コントローラにはコンポーネント(Component)、ビューにはヘルパー(Helper)という拡張要素が存在し、ビヘイビアから開発し始めるようなツワモノもいるようだが、慌てないでも大丈夫。いつか、俺も君もそうなる。
というわけで、ざっくりとBakeを解説してみた。
俺もCakePHP2.0のBakeは初めてなので、いろいろ面白かった。
ぜひみんなにもやってみて欲しいと思う。そしてわかったことを俺にそっと教えてくれれば、少なくともそっちに足を向けて寝ることは無いと思うんで、ぜひよろしくだ。
facebook
twitter
google+
fb share