CakePHPを利用した簡単アプリケーション開発 ~ Modelを使おう編 | A Day In The Boy's Life

A Day In The Boy's Life

とあるエンジニアのとある1日のつぶやき。

前回、CakePHPのControllerとViewの使い方 を説明していきましたが、今回はMVC最後となるモデル編です。

CakePHPのModelでは、DBとの関係を定義しデータの操作の振る舞いを隠蔽してくれる役割があります。



テーブルとModelの関係


今回対象とするテーブル(dogs)の構造は下記のようになっているとします(DBはPostgreSQLを使っています)。

テーブル名は、Controller名と同様に複数形で定義しておきます。


cakephp=# \d dogs
  Column  |            Type             |                     Modifiers                   
----------+-----------------------------+---------------------------------------------------
 id       | integer                     | not null default nextval('dogs_id_seq'::regclass)
 name     | character varying(50)       |
 created  | timestamp without time zone | default now()
 modified | timestamp without time zone | default now()

idはPrimary Keyにして採番はdogs_id_seqというシーケンスに任せるようにしています。

これに対して、下記のCakePHPのModelファイル(Dog.php)を用意します。


<?php

class Dog extends AppModel {
    public $name = 'Dog';
}


ファイル名やクラス名は、テーブル名とは違って単数形とし、クラスはAppModelのサブクラスとして定義します。

メンバ変数$nameはモデル名を定義します。

最も基本的なモデルを利用するために必要なものはこれだけです。


最後に、ControllerからModelを呼び出してみます。

前回作成しているPagesControllerを少し改良してみます。


<?php

App::uses('AppController', 'Controller');

class PagesController extends AppController {

    public $name = 'Pages';
    public $uses = array('Dog');

    public function display() {
        $this->set('dogs_data', $this->Dog->find('all'));
    }
}


コメントは取り払ったのですっきりしていますが、前回からの変更点はメンバ変数の$usesに利用するモデル名を配列で追加し、アクションdisplayの中でfindメソッド(データの参照)を利用してデータを取得し、View側にセットしています。


もし、dogテーブルにデータが投入されていれば、Viewを下記のように書くことで上記のモデルから取得したデータを出力することができます。

<?php foreach($dogs_data as $dog): ?>
<li><?php echo $dog['Dog']['name']; ?></li>
<?php endforeach; ?>
</ul>

画面の出力イメージは下記のようになります。

デバッグモードが有効になっていれば、下部に実際に実行されているSQLが表示されるので、それを元にデバッグしてみるのが効率的かもしれません。



A Day In The Boy&#39;s Life-CakePHPのモデル



Modelを使った複雑な問合せ


このようにModelを通すことで、開発者がわざわざSQLを書かなくてもデータ操作が行えるようになるわけですが、条件をつけたり複数のテーブルからデータを取得するといったことも可能です。


    public function display() {
        $options = array('fields'    => array('Dog.name', 'Dog.id'),
                         'conditions' => array('Dog.id' => 1));
        $this->set('dogs_data', $this->Dog->find('all', $options));
    }

上記の例では、nameとidカラムだけを対象にidが1のデータを取得するという条件付のデータ抽出を行っています。

検索条件のオプションは配列で指定しておき、findメソッドの第2引数にセットしていたオプションデータを指定します。

A Day In The Boy&#39;s Life-CakePHPのモデル2


デバッグで出力しているSQLも指定した条件に合わせて変化しているのがわかります。

他にもLIKE分を使ったあいまい検索を行うようなSQLもモデルを通して行うことができます。


    public function display() {
        $options = array('fields'     => array('Dog.name', 'Dog.id'),
                         'conditions' => array('OR' =>
                                            array(0 => array('Dog.name like' => '%dog%'),
                                                  1 => array('Dog.name like' => '%inu%'))),
                         'order'      => 'Dog.name');
        $this->set('dogs_data', $this->Dog->find('all', $options));
    }

モデルの使い方の基本は、決まったキーに配列でデータを詰め込んでいくだけです。

上記のfieldsはカラム名を、orderはデータの取り出し順を指定しています。



Modelを使って複数のテーブルからのデータ取得


もちろん、これまで解説してきたような単一のテーブルからのデータ取得だけでなく、複数のテーブルを外部キーと連結させて一度にデータを取得するといったこともできます。


例えば、先ほどまで利用していたDogsテーブルに加えて、犬の性格を表すデータを格納するcharactersテーブルがあったとします。

Charactersテーブルの「陽気」な犬に該当するデータをDogsテーブルから取得したいといった要件にあわせるため、Charactersテーブル用のモデルファイルを用意します。


<?php

class Character extends AppModel {
    public $name = 'Character';
    public $belongsTo = array('Dog' =>
                           array('className' => 'Dog',
                                 'foreignKey' => 'id')
                     );
}

最大の違いは、$belogsTo変数に、関連するテーブルや外部キーを定義しておくことです。

この定義により、Dogsテーブルのidカラムを利用してテーブルの結合を行います。

Controller側も上記にあわせて利用するモデルや条件を変更してみましょう。


<?php
App::uses('AppController', 'Controller');

class PagesController extends AppController {

    public $name = 'Pages';
    public $uses = array('Dog', 'Character');

    public function display() {
        $options = array('fields'     => array('Dog.name', 'Dog.id'),
                         'conditions' => array('Character.name' => '陽気'),
                         'order'      => 'Dog.name');
        $this->set('dogs_data', $this->Character->find('all', $options));
    }
}


利用するモデルが増えているので、$usesに追加しておきます。

条件はCharactersテーブルから指定するので変更し、find(検索対象)もDogからCharacterに変更しています。


これだけで、後は勝手にDogsテーブルと結合して、DogsテーブルにあるIDとか名前を取得してくれます。


A Day In The Boy&#39;s Life-CakePHPのモデル3


このようにモデルを使ってもある程度複雑な処理を簡単に記述することができます。

ただ、あまりに複雑な条件になってくるとモデルの定義や問合せ方法が煩雑になり、SQLではわかっているのに・・・、という状況に陥るかもしれません。

そんな場合は、素直にSQLを直接書いたほうがよいかもしれません。


 $dogs = $this->Dog->query("select id from dogs order by id desc");


Modelを通したデータの投入、更新


データの参照だけでなく、データの投入や更新もModelを通して簡単に行えます。
例えば、新たにデータを挿入したい(INSERT)場合は、下記のように書くだけです。


    public function display() {
        $data = array('Dog' => array('name' => 'Dachshund'));
        $this->Dog->save($data);
    }


元々、Dogsテーブルのidカラムはシーケンスから採番するようにしているので、特に値を指定する必要がありません。

nameカラムに投入したいデータを指定し、後はDogモデルのsaveメソッドに渡すだけでデータが投入されます。


更新も同じsaveメソッドを使って行えます。


    public function display() {
        $data = array('Dog' => array('name' => 'Bull Dog'));
        $this->Dog->id = 2;
        $this->Dog->save($data);
    }


saveの前にidを指定していますが、これが更新対象となります。

もし、このIDが存在しない場合はデータが投入されます。

他にも特定の条件にマッチする行を一気に更新したい場合は、updateAllを利用することできます。


    public function display() {
        $condition = array('id >' => 2);
        $this->Dog->updateAll(array('name' => "'Poodle'"), $condition);
    }

条件の書き方が少し特殊にはなりますね。

また、更新するカラムとその値を配列で指定しますが、文字列は’’で括る必要があります。


SELECT "Dog"."id" AS "Dog__id" FROM "public"."dogs" AS "Dog" WHERE "id" > 2


と、ここまで簡単な紹介をしてきましたが、CakePHPのModelでできることはかなり多くあるため、マニュアル もあわせて確認してください。