WHAT'S NEW?
Loading...

CakePHP2.0のコンソールを使ってラクラク自動生成しよう【2/2】

■前回の続き

前回はデータベーススキーマに関するコンソール操作を説明したんだけど、なかなか興味深い内容だったと思う。

今回はいわゆる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
ユーザを追加する際は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は初めてなので、いろいろ面白かった。
ぜひみんなにもやってみて欲しいと思う。そしてわかったことを俺にそっと教えてくれれば、少なくともそっちに足を向けて寝ることは無いと思うんで、ぜひよろしくだ。