CakePHP 便利な組み込みビヘイビア『ContainableBehavior』の使い方

cakePHPは、複数テーブルの結合を簡単に実現できます。
が、何も指定せずに『find』でデータを取り出すと、結合しているテーブルのデータも大量に取り出すことになります。

「このテーブルは、1つのフィールドからデータを取り出せればいいのに・・・」って時に活躍してくれるのが『ContainableBehavior』です。

取り出したいフィールドや、条件に一致する場合のみデータを取得なんてことが簡単にできるようになります。
そんな便利なビヘイビア『ContainableBehavior』の使い方を、簡単なブログ(?)みたいなシステムを例にして紹介したいと思います。

(モデルでの使用を例にしますが、コントローラーから使用する方法も基本的に同じです。)


▼簡単なブログシステムの構造

このブログシステムには以下の3つのテーブルがあります。

  • 【Post】
    ブログに投稿された記事を管理

  • 【User】
    ブログ利用者を管理

  • 【Comment】
    ブログに投稿された記事に対するコメントを管理

各テーブルの構造は、以下の通りです。

▼Postテーブル

`id` int(11) NOT NULL auto_increment,
`user_id` int(11) default NULL,
`mail` varchar(255) default NULL,
`title` varchar(50) default NULL,
`text` text,
`created` datetime default NULL,
`modified` datetime default NULL,
PRIMARY KEY  (`id`)

▼Userテーブル

`id` int(11) NOT NULL auto_increment,
`alias` varchar(50) default NULL,
`username` varchar(50) default NULL,
`password` varchar(70) default NULL,
`created` date default NULL,
`modified` date NOT NULL,
PRIMARY KEY  (`id`)

▼Commentテーブル

`id` int(11) NOT NULL auto_increment,
`post_id` int(11) default NULL,
`user_id` int(11) default NULL,
`mail` varchar(255) default NULL,
`title` varchar(50) default NULL,
`text` text,
`created` datetime default NULL,
`modified` datetime default NULL,
PRIMARY KEY  (`id`)

記事に対してコメントが連なる感じです。

あくまで『ContainableBehavior』の使い方を紹介するためだけに用意したシステムなので、細かい突っ込みなどは無しでよろしくお願いします・・・


1・準備

『ContainableBehavior』を使用するためには、モデルのメンバ変数『$actsAs』に『Containable』を追加します。

class Post extends AppModel {
    public $name   = 'Post';
    public $actsAs = array('Containable');
}

2・使い方

何も指定せず『find』などでデータを取得すると、以下のように大量のデータを取得してしまいます。

▼何も指定せず『find』した場合

Array (
    [0] => Array (
        [Post] => Array (
            [id] => 1
            [user_id] => 1
            [mail] => info@yasigani-ni.com
            [title] => ブログはじめました
            [text] => 初めてのブログ投稿です。
            [created] => 2011-10-8 18:00:00
            [modified] => 2011-10-08 18:00:00
        )

        [User] => Array (
            [id] => 1
            [alias] => ユーザー01
            [username] => test
            [password] => **********************
            [created] => 2011-10-8 18:00:00
            [modified] => 2011-10-08 18:00:00
        )

        [Comment] => Array (
            [0] => Array (
                [id] => 1
                [post_id] => 1
                [user_id] => 2
                [mail] => test01@yasigani-ni.com
                [title] => コメント
                [text] => 初めてのコメント投稿です。
		[created] => 2011-10-8 18:10:00
		[modified] => 2011-10-08 18:10:00
            )

            [1] => Array (
                [id] => 2
                [post_id] => 1
                [user_id] => 3
                [mail] => test02@yasigani-ni.com
                [title] => コメント
                [text] => 2つめのコメント投稿です。
		[created] => 2011-10-8 18:10:00
		[modified] => 2011-10-08 18:10:00
            )
        )
    )

    [1] => Array (
        [Post] => Array
	    ・・・

これを『Userテーブルからは、aliasだけを取得する』場合、以下のようにします。

▼Postモデルで指定する場合

public function find($type, $options = array()) {
    $data = parent::find($type, array_merge($options, array('contain' => array('User.alias'))));
    return $data;
}

データを取得する際に、オプションに『contain』に取得したいフィールド名を指定するだけです。
これでデータを取得すると、以下のようなデータになります。

▼結果

Array (
    [0] => Array (
        [Post] => Array (
            [id] => 1
            [user_id] => 1
            [mail] => info@yasigani-ni.com
            [title] => ブログはじめました
            [text] => 初めてのブログ投稿です。
            [created] => 2011-10-8 18:00:00
            [modified] => 2011-10-08 18:00:00
        )

        [User] => Array (
            [alias] => ユーザー01
        )

        [Comment] => Array (
            [0] => Array (
                [id] => 1
                [post_id] => 1
                [user_id] => 2
                [mail] => test01@yasigani-ni.com
                [title] => コメント
                [text] => 初めてのコメント投稿です。
		[created] => 2011-10-8 18:10:00
		[modified] => 2011-10-08 18:10:00
            )

            [1] => Array (
                [id] => 2
                [post_id] => 1
                [user_id] => 3
                [mail] => test02@yasigani-ni.com
                [title] => コメント
                [text] => 2つめのコメント投稿です。
		[created] => 2011-10-8 18:10:00
		[modified] => 2011-10-08 18:10:00
            )
        )
    )

    [1] => Array (
        [Post] => Array
	    ・・・

『User』からは、『alias』だけを取得しているのが分かると思います。

「これなら

『$this->Post->find('all', array('fields' => array('User.alias')));』

でいいんじゃない?」
と、お思いの方。

『ContainableBehavior』の実力は、こんなものじゃありません。

連結しているモデルの条件を指定したり、連結が複雑なモデルの場合などで力を発揮します。
単純に『recursive』の指定をしないで済むだけでも、とても楽になると思います。

簡単な条件を指定するには、以下のようにします。

▼簡単な条件指定

public function find($type, $options = array()) {
    $data = parent::find($type, array_merge($options, array('contain' => array('Comment.id = 1'))));
    return $data;
}

これで、idが1のCommentだけ取得することができます。
連結が複雑なモデルの場合でも、取得するフィールドを指定するだけならとても簡単です。
Commentが連結しているUserテーブルのaliasを取得する場合は、以下のようにします。

▼連結が複雑なモデルで取得するフィールドを指定

public function find($type, $options = array()) {
    $data = parent::find($type, array_merge($options, array('contain' => array('Comment.User.alias'))));
    return $data;
}

Commentには、idが1のCommentだけを取得。Userからはaliasを取得したい場合は以下のようにします。

▼条件指定と取得するフィールドの指定

parent::find('all', array_merge($options, array('contain' => array(
                'Comment' => array(
                    'conditions' => array('Comment.id = ?' => 1),
                ),

                'User' => array(
                    'fields' => array('User.alias'),
                ),
            )
        )
    )
);

通常のfindを使う感覚で、簡単に条件指定することができます。
連結が複雑なモデルの場合のは以下のようになります。
例えば、『Commentはtitleと、連結しているUserからaliasを取得する』『Postと連結しているUserからはaliasを取得』したい時は、

parent::find('all', array_merge($options, array('contain' => array(
                'Comment' => array(
                    'fields' => array('Comment.title'),
                    'User' => array(
                        'fields' => array('User.alias')
                    ),
                ),

                'User' => array(
                    'fields' => array('User.alias'),
                ),
            )
        )
    )
);

といった感じになります。


最後に

連結が複雑なモデルでも、取得するフィールドの指定だけならとても簡単です。
複雑な指定や連結が複雑なモデルだと配列だらけで読みにくくなってしまいますが、連結を意識したインデントなどで対処できると思います。

『ContainableBehavior』をまだ使用したことがなく、私の説明を読んだら余計に混乱した方や、難しそうと思った方。
私の文章力が悪いだけなので、だまされたと思って一度使用してみてください。
すごい楽に開発できるようになると思います。

マニュアルはこちらから

6.2 コンテイナブル