CakePHP 2.x系におけるSchemaについて

CakePHP 2.x系におけるSchemaについて
====

# スキーマファイルの使い方

スキーマファイルは、プロジェクトで使用するデータベースのテーブル定義（テーブルの構造）をphpで記述したものです。現在のデータベースのテーブル定義にもとづき、その内容をスキーマファイルとして保存することが出来ます。

この機能は、複数人でプロジェクトを進める際に有用です。開発者1と開発者2で、プロジェクトを開発しているとすると、開発者1が自分で作成したテーブルをスキーマファイル(schema.php)に変換して、gitリポジトリにコミットすることが出来ます。開発者2は、リポジトリをcloneして、スキーマファイル(schema.php)に基づいて、テーブルを作成することが出来ます。

同様のことは、テーブル定義をdumpすることでも可能です。ただし、dump -> restoreの場合、データベースを1から作り直すことになります。このとき、開発中に挿入したレコードも一度すべて消えてしまうため、開発中に気軽に更新することができません。

一方、スキーマファイルを利用すると、現在のデータベースと、スキーマファイルに記述されている内容を比較して、足りないテーブルや変更されたテーブルのみを生成・修正することが出来ます。よって、開発中に他の開発者が加えた変更を、自分の作業環境に反映することが気軽に行うことが出来ます。

また基本的に、スキーマファイルでは、レコードについては関与しませんが、スキーマ処理のbeforeスクリプト、afterスクリプトが設定出来るので、スクリプト内で初期データのようなレコードを挿入する記述を行うことが可能です。

スキーマファイルの仕組みは、マイグレーション(CakeDC Migration)と異なり、一ファイルでデータベース全体をカバーしているので、複数の開発者がそれぞれテーブル追加、修正の変更を行った場合、スキーマファイルのコンフリクトが発生します。これが発生した場合は、コンフリクト解決を手作業で解決しなくてはいけません。


# DBテーブルからスキーマファイルの作成

Cakeのプロジェクトを作成したばかりの状態では、`Config/Schema/schema.php`ファイルは存在しません。

## スキーマファイルの作成
次のコマンドで、`schema.php`ファイルを作成します。

    $ Console/cake schema generate -f

このコマンドは、現在のDBのテーブル情報をもとに、`schema.php`ファイルを生成します。
(`-f`は、CakeのModelとひもづいていないテーブルも、スキーマの対象にする場合につけます。
通常は、いつも`-f`をつけておけば良いと思ってください。)

## 空のスキーマファイルの作成
まだテーブルが何もない状態で

    $ Console/cake schema generate -f

を実行すると、`Config/Schema/schema.php`が生成されて、次のようになります。

    <?php 
    class AppSchema extends CakeSchema {
    
            public function before($event = array()) {
                    return true;
            }
    
            public function after($event = array()) {
            }
    
    }

テーブルがなにもない状態なので、空の`schema.php`ファイルが出来ました。

## テーブルが存在する場合のスキーマファイルの作成
次に、SQLコマンドを実行して、テーブルを一つ作成してみます。

    CREATE TABLE person
      ( id int not null auto_increment, 
        name text,
        primary key (id)
      );

この状態で、再度

    $ Console/cake schema generate -f

を実行してみます。

すでに、Config/Schema/schema.phpファイルが存在するので、このファイルを上書き(Overwrite)するか、それともこれとは別ファイルとして保存(Snapshot)するかを確認してきます。

    Schema file exists.
     [O]verwrite
     [S]napshot
     [Q]uit
    Would you like to do? (o/s/q) 

O(Overwrite)を選択すると、既存のschema.phpファイルが上書きされます。
S(Snapshot)を選択すると、新しくschema_1.phpファイルが作成されます。(すでにschema_1.phpファイルが存在する場合は、schema_2.phpファイルが作成されます)

いずれにしても、新しいスキーマファイルは次のようになります。

    <?php 
    class AppSchema extends CakeSchema {
    
            public $file = 'schema.php'; // Overwriteを選択したときは、ここが'schema_1.php'のようになります。
    
            public function before($event = array()) {
                    return true;
            }
    
            public function after($event = array()) {
            }
    
            public $person = array(
                    'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'unsigned' => false, 'key' => 'primary'),
                    'name' => array('type' => 'text', 'null' => true, 'default' => null, 'collate' => 'utf8_general_ci', 'charset' => 'utf8'),
                    'indexes' => array(
                            'PRIMARY' => array('column' => 'id', 'unique' => 1)
                    ),
                    'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'InnoDB')
            );
    
    }
    
パブリックフィールドの`$person`が、personテーブルの定義になります。

今回、personテーブルは空でしたが、仮にpersonテーブルにレコードがあったとしても、レコードの内容はスキーマファイルには反映されません。


# スキーマファイルからDBテーブルの反映

## 既存のテーブルを削除して作成

既存のテーブルを削除して作成する場合、

    $ Console/cake schema create

を実行する。すると、次のように既存のテーブルを削除して良いかの確認が出る。（以下は、スキーマファイル内に`animal`テーブル、`book`テーブル、`person`テーブルがある場合）

    The following table(s) will be dropped.
    animal
    book
    person
    Are you sure you want to drop the table(s)? (y/n) 

テーブルをdropして良ければyを、ダメであればnを選択する。削除する場合、表示されたすべてのテーブルが全部削除されてしまい、個別にy/nを選べるわけではないので注意。
yを選択した場合、次のように削除処理が実行される

    Dropping table(s).
    animal updated.
    book updated.
    person updated.

なお、yを選択した場合であっても、スキーマファイルに記述されていないテーブルがDB側にあった場合、そのテーブルは削除されない。

次に、今度はテーブルを生成して良いのかの確認が出る

    The following table(s) will be created.
    animal
    book
    person
    Are you sure you want to create the table(s)? (y/n) 

テーブルを生成して良ければyを、ダメであればnを選択する。yを選択すると、テーブルの生成が実行される。


    Creating table(s).
    animal updated.
    book: updated.
    person updated.

仮に、drop処理でnを選択し、テーブル生成時点で同名の既存テーブルが存在している場合、そのテーブルの生成処理は実行されない。たとえば、bookテーブルがすでに存在している場合、

    Creating table(s).
    animal updated.
    book: SQLSTATE[42S01]: Base table or view already exists: 1050 Table 'book' already exists
    person updated.

となる。

## 既存のテーブルとスキーマファイルを比較して、更新

テーブルの削除・新規作成ではなく、現在のテーブルとスキーマファイルを比較して、不足分を更新（修正）する場合は、

    $ cake schema update

を実行する。

例えば、bookテーブルのみがない状態で上記を実行すると、

    Comparing Database to Schema...
    
    The following statements will run.
    CREATE TABLE `cakeweb2`.`book` (
        `id` int(11) NOT NULL AUTO_INCREMENT,
        `name` text CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
        `price` int(11) DEFAULT NULL,   PRIMARY KEY  (`id`))    DEFAULT CHARSET=utf8,
        COLLATE=utf8_general_ci,
        ENGINE=InnoDB;
    Are you sure you want to alter the tables? (y/n) 

と実行されるSQLコマンドが表示されるので、問題なければyを、変更してはダメであればnを選択する。
yを選択した場合、次のように表示されて、更新処理が実行される。

    Updating Database...
    book updated.
    End update.

## スキーマファイルを指定してcreate/updateする。

これまでは、`schema.php`ファイルが対象となり、createやupdateを行ってきた。
もしも、Snapshotで作成した`schema_1.php`や`schema_2.php`のような、バージョン付きのファイルを対象する場合、`-s`オプションを使う。

例えば

    $ Console/cake schema create -s 1

とすると、`schema_1.php`が対象となり、テーブルの作成が実行される。
同様に

    $ Console/cake schema update -s 2

とすると、`schema_2.php`が対象となり、テーブルの更新が実行される。

# フックスクリプトを使って、レコードを追加する

 afterフックスクリプトを使って、レコードを追加することができます。この場合、手作業でschema.phpファイルを編集する必要が有ります。
また、モデルクラスを通してレコードを作成するので、モデルクラスも必要になります。

 例えば、personテーブルにレコードを追加したい場合、Personクラスが必要です。例えば、`person.php`ファイルは次のようになります。

    <?php
    class Person extends AppModel {
    
    }

次に、スキーマファイルのafterスクリプト(afterメソッド)を手で編集して

    public function after($event = array()) {
        if (isset($event['create'])) {
            switch ($event['create']) {
                case 'person':
                    App::uses('ClassRegistry', 'Utility'); // 実際にレコードが追加される処理開始
                    $post = ClassRegistry::init('Person');
                    $post->create();
                    $post->save(
                        array('Person' =>
                            array('name' => 'Phper')
                        )
                    );
                    break;
            }
        }
    }

のようにします。`$event`変数は、スキーマファイルの呼び出し方(createやupdateなど)をキーとして、また対象となるテーブルをバリューとした
配列です。
上記では、createイベントで、personテーブルが対象となってときに、

                    App::uses('ClassRegistry', 'Utility'); // 実際にレコードが追加される処理開始
                    $post = ClassRegistry::init('Person');
                    $post->create();
                    $post->save(
                        array('Person' =>
                            array('name' => 'Phper')
                        )
                    );
                    break;

の部分が実行されます。

よって、例えば

    $ Console/cake schema create

であれば上記コードは実行されますが、

    $ Console/cake schema update

では実行されません。

# スキーマファイルをsqlファイルに変換

スキーマファイルをdumpして、sqlファイルにすることができます。例えば、`filename.sql`ファイルとして出力したいのであれば

    $ Console/cake schema dump --write filename.sql

を実行します。`Config/Schema`以下に、次のような`filename.sql`ファイルが生成されます。

    CREATE TABLE `cakeweb2`.`animal` (
            `id` int(11) NOT NULL AUTO_INCREMENT,
            `name` text CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
            `weight` int(11) DEFAULT NULL,  PRIMARY KEY  (`id`))    DEFAULT CHARSET=utf8,
            COLLATE=utf8_general_ci,
            ENGINE=InnoDB;
    
    CREATE TABLE `cakeweb2`.`book` (
            `id` int(11) NOT NULL AUTO_INCREMENT,
            `name` text CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
            `price` int(11) DEFAULT NULL,   PRIMARY KEY  (`id`))    DEFAULT CHARSET=utf8,
            COLLATE=utf8_general_ci,
            ENGINE=InnoDB;
    
    CREATE TABLE `cakeweb2`.`person` (
            `id` int(11) NOT NULL AUTO_INCREMENT,
            `name` text CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,    PRIMARY KEY  (`id`))    DEFAULT CHARSET=utf8,
            COLLATE=utf8_general_ci,
            ENGINE=InnoDB;