WHAT'S NEW?
Loading...

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

■はじめに

CakePHP2.0を使えば、基本的な習得コストを除き、高速にWebアプリケーションの開発ができるんだよ。
高速開発を目指して始めたCakePHPだけど、今では2.0。当時1.1から始めてたけど、機能も豊富になり、洗練されてきた。
ここらで一旦基本に戻り、CakePHP2.0の使い方で新しい発見が無いか試して見ることにしたんだ。

もしかしたら高速を超えた、超高速な開発が実現できるかもしれない。むしろそれを目指そう。

実際の作業を行う前に説明しておくけど、Windowsでの作業は説明しないので、Windows環境しか無い人は、VirtualBoxでFedora入れるなりしてLinux環境を構築しておくことをすすめる。



XAMPPなんかのナンチャッテLinuxで動かすことは全く想定してないんで、あしからず。
ちなみにここらへんの俺の考えは過去記事に書いてあるので興味がある方は以下リンクをクリックされたし。

さて、早速行ってみよう。

■前提条件
  • 使用するOSはLinuxとする
  • Apache、MySQL、PHPなどの設定は適切に完了していることとする
  • CakePHP2.0のデプロイが完了していてブラウザで閲覧できていることとする
  • インストール先は/cakephp2.0/htdocs/とする
  • cakeコマンドは/cakephp2.0/htdocs/app/Consoleに移動しているものとする

■CakePHP2.0のセットアップ
  1. CakePHP2.0をダウンロード
  2. パーミッションの設定
  3. ブラウザで確認
上記は終わらせておいてくだされ。

■データベースをつくろう

MySQL WorkbenchやphpMyAdminなどのGUIツールでデータベース設計しながらテーブル作る、なんてことも楽しいとは思うんだけど、出来れば終始、一つのプラットフォームで同じ入力と出力を期待したい。

俺の場合、エディタはIDEであるNetBeansを使って開発しているので、端末ウィンドウを表示させ、そのコンソール画面でコマンドラインでデータベースを作るようにしたいんだ。多くの作業がこのコンソールで実現可能なのであれば、いちいち他のツールを起動しなくて済むので、細かい話だけど手間が省けると思う。

CakePHPでは、app/Config/Schema/の中にスキーマ定義ファイルであるschema.phpが存在すればその内容でテーブルを作ることもできるし、既存のデータベースからschema.phpを自動生成させることも可能だ。

まず、既存のデータベースからusersテーブルをschema.phpにジェネレートし、schema.phpがどういう内容になっているのかを確認したいので、usersテーブルを以下のSQLで作っておこくことにする。
CREATE TABLE IF NOT EXISTS `users` (
  `id` int(5) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL,
  `passwd` varchar(40) NOT NULL,
  `nickname` varchar(36) NOT NULL,
  `is_enabled` tinyint(1) unsigned NOT NULL,
  `is_deleted` tinyint(1) unsigned NOT NULL,
  `created` datetime NOT NULL,
  `modefied` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `users_idx` (`username`,`passwd`,`nickname`,`is_enabled`,`is_deleted`,`created`,`modefied`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
この状態で、コンソールから以下のコマンドを実行する。
$ ./cake schema -app /cakephp2.0/htdocs/app generate -f
cake schema -app <appの場所> generate -f
-fをつけると全テーブルを対象にしてくれる
そうするとコンソールには以下のように表示され、スキーマファイルが生成されたことがわかる。
Welcome to CakePHP v2.0.5 Console
---------------------------------------------------------------
App : app
Path: /cakephp2.0/htdocs/app/
---------------------------------------------------------------
Cake Schema Shell
---------------------------------------------------------------
Generating Schema...
Schema file: schema.php generated
もしすでにschema.phpが存在した場合、以下のように、どうするのかを聞かれる。
Generating Schema...
Schema file exists.
[O]verwrite
[S]napshot
[Q]uit
Would you like to do? (o/s/q) 
[s] > 
上書きするなら「o」、スナップショット(後述)なら「s」、この作業をやめるなら「q」を入力する。
スナップショットを選択すると、現在存在するschema.phpとは別に、schema_1.php、schema_2.phpと連番で別名のファイルを生成してくれる
というわけで、早速、/app/Config/Schema/schema.phpを見てみよう。
見るのにもcakeコマンドでいける。
./cake schema -app /cakephp2.0/htdocs/app view
まぁコンソールでソース見ることはあまりないと思うので、素直にファイルを開いてもOKだと思う。
/* generated on: 2012-01-25 13:55:47 : 1327467347 */
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'),
    'passwd' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 40, 'collate' => 'utf8_general_ci', 'comment' => '', 'charset' => 'utf8'),
    'nickname' => array('type' => 'string', 'null' => false, 'default' => NULL, 'length' => 36, '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', 'passwd', 'nickname', 'is_enabled', 'is_deleted', 'created', 'modefied'), 'unique' => 0)),
    'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB')
  );
}
大体こんな内容になっているはずだ。
この中の$usersプロパティに、usersテーブルの構造が収まっている。

要するにこのプロパティを手で書いてしまえれば、MySQL WorkbenchやphpMyAdminなんかは使わなくて済む、というわけだ。

簡単に解析してみる。

プロパティ名は$usersで、これがこのままテーブル名になる。
配列のキーはそのままフィールド名になっていて、その中も配列になっている。中身はフィールドの内容だ。
  • type
  • null
  • default
  • langth
  • key
  • collate
  • comment
これはただ単にフィールド名の情報なので、そんなに大変じゃないと思う。

あとはインデックスだ。インデックスはプロパティ内ではindexesというキーになっていて、その中身は、
  • PRIMARY
  • <インデックス名>
などなど。

そして最後にテーブル自体の情報のtableParametersだ。
  • charset
  • collate
  • engine
設定する内容が多いので暗記するには少々骨が折れそうだが、CakePHP2.0使いなら一度はやってみても良いと思う。

今度は逆に、このschema.phpに$profilesプロパティを設定し、profilesテーブルを作って見ることにする。
以下のコードをschema.phpに追記だ。

まずはフィールド名を固めておき、インデックスとテーブルのプロパティを記述してみる。
public $profiles = array(
    'id' => array(),
    'user_id' => array(),
    'image_url' => array(),
    'created' => array(),
    'modified' => array(),
    'indexes' => array(),
    'tableParameters' => array()
  );
そして肉付けだ。
public $profiles = array(
    'id' => array(
      'type' => '',
      'null' => false,
      'default' => null,
      'length' => 5,
      'key' => 'primary',
      'collate' => null,
      'comment' => ''
    ),
    'user_id' => array(),
    'image_url' => array(),
    'created' => array(),
    'modified' => array(),
    'indexes' => array(),
    'tableParameters' => array()
  );
最終的に以下のように記述してみた。
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' => ''
    ),
    'image_url' => array(
      'type' => 'string',
      'null' => false,
      'default' => null,
      'length' => 128,
      'key' => 'index',
      'collate' => 'utf8_general_ci',
      'comment' => 'アバター画像URL'
    ),
    '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', 'image_url', 'created', 'modified'), 'unique' => 1),
    ),
    'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB')
  );
少々見た目が悪いが、部分的に$usersプロパティからコピーしたので、改行が入ってない部分がある。
これを今度はcreateサブコマンドで、テーブルとしてデータベースに登録させてみよう。
./cake schema -app /cakephp2.0/htdocs/app create
そうすると、以下のように聞かれる。
Welcome to CakePHP v2.0.5 Console
---------------------------------------------------------------
App : app
Path: /cakephp2.0/htdocs/app/
---------------------------------------------------------------
Cake Schema Shell
---------------------------------------------------------------

The following table(s) will be dropped.
users
profiles
Are you sure you want to drop the table(s)? (y/n) 
[n] > 
テーブルを一旦削除していいかどうか聞かれる。
出来ればプロパティ変数を個別に指定してテーブル毎に作成してもらいたいけど、ヘルプを見る限り、細かい指定は出来なさそう。
「n」と答えて削除しないでやってみたら、「usersテーブルすでにあるから新たに作れねぇぞボケェェェ!!」と怒られた。
素直に「y」で削除しようじゃないか。
The following table(s) will be dropped.
users
profiles
Are you sure you want to drop the table(s)? (y/n) 
[n] > y
Dropping table(s).
users updated.
profiles updated.

The following table(s) will be created.
users
profiles
Are you sure you want to create the table(s)? (y/n) 
[y] > y
Creating table(s).
users updated.
profiles updated.
End create.
さて、実際にphpMyAdminなどで見てみると、ちゃんとテーブルが生成されているのがわかる。


一つ残念なのが、cake schemaコマンドではフィールド属性で指定した『unsigned』が無視されてしまっているところだ。

試しに「extra」キーを与えて中身を「unsigned」にしても、ダメだった。
unsignedが使えないとなると、フィールド内で扱えるバイト数が半分になってしまうので、あとで直さないといけない。面倒くさい。

これは今後のschemaのバージョンアップに期待したいところ。

※この問題はマイルストーンが『いつか』扱いになっているようだ

■SQLファイルをダンプしてみよう

実はcake schemaコマンドにはSQLダンプコマンドがある。
phpMyAdminなどのGUIツールでダンプしたSQLファイルに比べると恐ろしくしょぼいが。
$ ./cake schema --app /cakehp2.0/htdocs/app dump --write schema.sql
cake schema -app <appのパス> dump --write <sqlファイル名>
出力されたSQLファイルを見てみると、そのシンプルさに驚くと思う。
#App sql generated on: 2012-01-25 17:13:47 : 1327479227

DROP TABLE IF EXISTS `profiles`;
DROP TABLE IF EXISTS `users`;

CREATE TABLE `profiles` (
  `id` int(5) NOT NULL AUTO_INCREMENT COMMENT '',
  `user_id` int(5) NOT NULL COMMENT '',
  `image_url` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'アバター画像URL',
  `created` datetime NOT NULL COMMENT '',
  `modified` datetime NOT NULL COMMENT '', PRIMARY KEY  (`id`),
  UNIQUE KEY `profile_idx` (`user_id`, `image_url`, `created`, `modified`)) DEFAULT CHARSET=utf8,
  COLLATE=utf8_general_ci,
  ENGINE=InnoDB;

CREATE TABLE `users` (
  `id` int(5) NOT NULL AUTO_INCREMENT COMMENT '',
  `username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '',
  `passwd` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '',
  `nickname` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '',
  `is_enabled` tinyint(1) NOT NULL COMMENT '',
  `is_deleted` tinyint(1) NOT NULL COMMENT '',
  `created` datetime NOT NULL COMMENT '',
  `modefied` datetime NOT NULL COMMENT '', PRIMARY KEY  (`id`),
  KEY `username` (`username`, `passwd`, `nickname`, `is_enabled`, `is_deleted`, `created`, `modefied`)) DEFAULT CHARSET=utf8,
  COLLATE=utf8_general_ci,
  ENGINE=InnoDB;
コンソール上からmysqlコマンドでインポートさせるケースなんかはあるかもしれないけど、スキーマ定義ファイルで事足りるという。
ただ、ダンプしてSQLファイルにしておくと、CakePHP以外でも使えるよね。

■構造のアップデート

cake schemaコマンドにはupdateというサブコマンドも用意されているんだけど、これが何度やってもうまく行かない。

例えばschema.phpを開いて、profilesにwebsite_urlなど1つ足して保存したとする。
この状態でupdateしたら、profilesテーブルにwebsite_urlフィールドがalter tableされて追加されるぞ!と期待していた。

しかし実際にはすべてのフィールドをaddしてしまい『ユニークな主キーが重複しているからアップデートできないぜ(#゚Д゚)ゴルァ!!』になる。

英語のマニュアルを読んでみると、どうもupdateはロールバックするときに使うようで、基本、createでやるようだ。

ただ、この部分正直いうとあまり自信がないので、より詳しい情報が入り次第、記事を更新しようと思う。


■まとめ

正直schemaファイルを自作するとなると覚える個数がかなり多いが、たかだか程度の問題。
複雑なロジックを知識として覚え、そこから知恵を捻出するような作業ではないので、単純に暗記でいいと思う。少なくとも俺は暗記しようと思う。

1つを表現するための方法は複数あったほうが良いとおもうし、逆に1つの方法で複数を表現できるというのもあり。
1つの方法で1つの表現しかできないのがコストパフォーマンスの低下を生むと思うんだよな。
というわけで、今回はスキーマでした。

次回はモデルとコントローラをやり、最後にPHPUnitを使ったテストを生成してみる予定。