ECWorks Blog

ECWorks Blog

CakePHPを中心としたサイト開発情報をメインに公開。新しもの好きなので時々製品レポートなんかも。

【CakePHP】お手軽便利なCakeSchema

cake-logoDBのテーブル設定は非常に面倒な作業の一つです。
特に、開発時は仕様変更などでテーブル内のフィールドが頻繁に増減することもあるかもしれません。

テーブルを作成したり、更新したりするのに、皆さんはどのような手順を踏まれるでしょうか?まずSQLを書いて、アップロードして、mysqlやpsqlのコンソールを使って実行していますでしょうか?それとも、mysqladminとかのguiツールを使っていますでしょうか?

CakePHPには、schemaシェルが付属されていて、これを用いることで簡単にテーブルを初期化することができます。コマンドラインからコマンド一発で(実際には確認メッセージがあるのでy/n選択がありますが)、書き換わるので大変に便利です。
ただ、ドキュメントや情報が公開されているブログなどが少ないため、どのように記述して良いか分からない方も多いかと思います。そこで、簡単に使い方を解説し、今回の目玉である「初期値を登録する方法」も伝授しちゃいます。

■schemaシェルの使い方

schemaシェルは、cakeコンソール内から呼び出されます。基本的な使用方法は次の通りです。

cake schema [-app (app名)] run create [(スキーマ名)] [(テーブル名)]

スキーマ名が無指定だと、AppSchemaが使われ、テーブル名が無指定だと全てのテーブルを処理します。
ちなみに、最初の「y/n/q」は「現在あるテーブルをドロップするか?」の問い合わせ、次が「新しくテーブルを作るか?」の問い合わせです。

また、現在設定されているテーブルを解析して、AppSchemaを作ることもできます。

cake schema [-app (app名)] generate [-f]

なお、AppSchemaはschema.phpとして、「app/config/sql」に保存されます。
自前で作成する場合も、基本的にはここに作成します。
また、標準では2回目以降は、スナップショットをとるか聞いてきますが、「-f」オプションをつけるとオーバーライトします。

※ほかにもオプションなどがありますので、詳しくはusageをご覧ください。

■schema.phpの記述方法

schema.phpは、次のようなクラスを記述します。


<?php
class AppSchema extends CakeSchema {
var $name = 'App';

//beforeコールバック
//
function before($event = array()) {
return true;  //正常終了はtrueを与える
}

//afterコールバック
//
function after($event = array()) {
}

//各テーブル(この場合はusersテーブル)
//
var $users = array(
'id' => array(
'type' => 'integer',   //型(int型)
'null' => false,   //nullがOKか?(falseなので不許可)
'default' => null,  //デフォルト値(null)
'key' => 'primary',  //インデックス(primaryを指定)
'extra' => 'auto_increment',  //auto_increment指定
'length' => 10,  //型の長さ(int(10))
),
'created' => array(
'type' => 'datetime',
'null' => false,
'default' => null,
),
'modified' => array(
'type' => 'datetime',
'null' => false,
'default' => null,
),

'name' => array(
'type' => 'string',  //string = varchar型
'null' => false,
'default' => null,
'length' => 255,  //= varchar(255)
),

'indexes' => array(
'PRIMARY' => array(
'column' => 'id',  //対象カラム
'unique' => true,   //ユニークなインデックスの場合はtrueを
),
'user_name_idx' => array(
'column' => array('id', 'name'),  //カラムが複数の場合はarrayで
'unique' => true,
),
),
);

}

型は、データベースの種類によって違いますが、おおむね次のような型を指定できます(下記はmysqlのもの)。
各DBのdatasourceの最初の方に定義されていますので、お使いのDBでどんな型が使えるか、確認してみてください。

string  => varchar
text
integer  => int
float
datetime
timestamp
time
date
binary  => blob
boolean  => tinyint

定義されていない型は、string(varchar)と見なされるようです。
※これはdatasource由来のものなので、schemaだからということはありません。

■初期値を簡単に与える方法

さて、ここまではソースを参照すれば何となく分かる範囲だと思いますが、ここからはECWorksオリジナルのTipsです。

このようにしてテーブルの初期化はできますが、マスタ(定数)を登録しておきたい場合もあると思います。毎回値を手入力するのは大変ですよね。
そこで、schema.php内にある「after()」コールバックで定義してしまおう、というのが今回の趣旨です。

しかし、一つ困った点があります。それは「schema.php内に記述したプロパティは、全部テーブルを作るのに使われてしまう」のです。さらに困ったことに、ご丁寧にも「テーブルとして登録したデータは、プロパティから削除されてしまう」のです。つまり、schema.php内に、定義したいデータを書くことができないのです。

そこで、「schema.phpではない場所にデータを書いて、schema.phpから呼び出してしまう」ことで、上記問題を解決してしまいます。

まず、after()コールバックに、次のようなコードを記述します。


function after($event = array()) {
//初期値ツールの呼び出しと実行
//
if(!empty($event['create'])){
if(!isset($this->InitialValues)){
require_once($this->path.DS.'initial_values.php');
$this->InitialValues = new InitialValues();
}
$modelname = Inflector::classify($event['create']);
$this->InitialValues->set($modelname);
}
}

これは、テーブルのcreate時にのみ、InitialValuesというクラスを呼び出し、$event[‘create’]に書かれているテーブル名からモデル名を作成し、そのモデルを呼び出してデータ登録を行います。
そして、InitialValuesクラスは次のように記述します。


<?php
class InitialValues extends Object {

//各モデルの初期値
//
var $values = array(
'User' => array(
array(
'name' => '麻生',
),
array(
'name' => '鳩山',
),
array(
'name' => '志位',
),
array(
'name' => '綿貫',
),
array(
'name' => '福島',
),
);

//初期化処理
//
function startup($schema = null){
if($schema === null){
return false;
}
}

//セット関数
//
function set($modelname){

if(!empty($this->values[$modelname])){
$this->{$modelname} = ClassRegistry::init($modelname);
$this->{$modelname}->saveAll($this->values[$modelname]);
}
}
}

今回はUserしか書いていませんが、複数テーブルがある場合は同階層で列挙していきます。
欠点は、model名とテーブル名をcake規約で書いていない場合、このやり方ではうまくいきません。別途テーブルを作るとかの改造が必要です。
また、配列を工夫すれば、アソシエーション付きのデータを登録することもできると思います。

以上で簡単な説明になりますが、コマンドラインが使え、なおかつ複雑なテーブル定義をしない(Cakeで一般的とされているテーブル定義で事足りる)場合は、上記手法はなかなかおすすめかと思います。
なお、現時点のバージョン(1.2.4)では、エラーがコールバック内で参照できないため、その点も注意が必要かもしれません。

schemaはまだまだ発展途上の機能かと思われますが、それは大勢の方に使われていないからだと思います。この情報を公開することで、schemaの利用が活発になり、よりよいものになってくれればと期待します。


Tagged as: ,

4 Comments

  1. テーブルに対応するModelを未作成の状態で、複数のtableに初期値を流し込もうとしてちょっとはまりました(^^;
    うまい方法が見つからなかったので、強引に
    $this->{$modelname}->_schema = null;
    とかして逃げました。もっとスマートな方法はないのかなぁ(^^;。

  2. あーなるほど、modelがないとうまく動かないかもですね。

    bakeでも何でも良いので、初期値を簡単に入れられる仕組みが確立されると良いんですよねー
    「SQLで良いじゃない?」と言われるかもしれないのですが、逆にSQLだと困るケースもあるので。
    CakeScehmaが今後発展することを祈りたいです。

    ちなみに、プロパティ全部がSchema内部のテーブル情報に食われてしまう問題は、どうやら1.3で改善される方向で進んでいるようです。チケット投げて良かった!

  3. まぁ,空のModel作っちゃえば良いんですけどね(^^;。

    既に動いているプロジェクトは、sqlでやっていましたが何かと不便だったのでお試し中です。
    なかなか良いですよね。

    > ちなみに、プロパティ全部がSchema内部のテーブル情報に食われてしまう問題は、どうやら1.3で改善される方向で進んでいるようです。チケット投げて良かった!

    おぉ!すばらしい!
    1.3は大きな改変はないようですが、細かいけど便利な機能が増えそうで楽しみですね!

  4. SQLの方が確かに細かいことが出来るのですが、プログラムで扱えることになるので幅が広がりますよね。
    1.3がどういう方向で進化するかはまだ見えていないのですが、より便利になると良いですね。