独自のモデルを作ってマッピングする(queryを使う)

CakePHPの場合、モデルとテーブルが1対1と想定しているので、これ以外のマッピングをする場合は、独自の Model を作ったほうがいいと思います。

例えば、先の記事のように、出版社(Publisher)から必ず都道府県(State)の名前を参照するのであれば、あらかじめそういう Model を作ってしまったほうが利便性が高いかな、と。

このような関係があるとき、雑誌の一覧を次のように取得したいと思う訳です。

  • 雑誌ID
  • 雑誌名
  • 出版社名
  • 出版社の所在地/都道府県

これを通常ならば、データベースのビューを作って、

  • id
  • magazine_name
  • publisher_name
  • publisher_state

のような形で取得しても良いのですが、環境によってはデータベースを変更できなかったり、データ管理は別の会社が構築したものだったりして、データベースとプログラムの間には大きな溝があるのが常だったりします。

# 小さなプロジェクトならばいいんですけどね。
# あと、もの分かり良い(以下略

という訳で http://servername/magazines/lists でアクセスできるようにします。
まずは、ビューを作成して、普通のモデルを使っているように書きます。というか書きたいわけです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- views/magazines/lists.ctp -->
<h2>雑誌一覧(独自)</h2>
<table>
    <tr>
        <td>id</td>
        <td>name</td>
        <td>Publisher.name</td>
        <td>State.name</td>
    </tr>
<?php foreach($MagazineLists as $item) : ?>
    <tr>
        <td><?php echo $item['Magazine']['id'] ?></td>
        <td><?php echo $item['Magazine']['name'] ?></td>
        <td><?php echo $item['Publisher']['name'] ?></td>
        <td><?php echo $item['State']['name'] ?></td>
    </tr>
<?php endforeach ; ?>
</table>

これに合わせて、コントローラーに lists メソッドを追加します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class MagazinesController extends AppController {
    var $name = 'Magazines';
    var $uses = array('Magazine','Publisher','MagazineList');
    function index() {
        $this->set('Magazines',$this->Magazine->find('all'));
        $this->set('Publishers',$this->Publisher->find('all'));
    }
    // 独自の雑誌リスト ★
    function lists() {
        $this->set('MagazineLists',$this->MagazineList->findAll());
    }
}
?>

findAll メソッドを使っているのは、他のモデルと揃えるためです。いわゆる元のメソッドをオーバーライドするという奴です。
モデルのクラス名は「MagazineList」(ファイル名は命名規則で magazine_list.php)になります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!-- models/magazine_list.php -->
<?php
class MagazineList extends AppModel
{
    var $name = 'MagazineList';
    var $useTable = false;    // ★テーブルに連結させない
 
    function findAll()
    {
        $sql = <<< HERE
SELECT
 Magazine.id,
 Magazine.name,
 Publisher.id,
 Publisher.name,
 State.id,
 State.name
FROM
 magazines as Magazine
 inner join publishers as Publisher
  on ( Magazine.publisher_id = Publisher.id )
 inner join states as State
  on ( Publisher.state_id = State.id )
 order by
  Magazine.id
HERE;
        return $this->query($sql); // クエリを実行する
    }
 
}
?>

独自にクエリを実行するので、Mode::query メソッドを使います。極悪ですねw。
クエリ文自体は、ヒアドキュメントにしておいて、MySQL Workbench などでテストできるようにしておくのがミソです。

あとは、結果を返す時に、「Magazine.id」のように返しておくと(多分asで別名を定義してもOKかと) $item[‘Magazine’][‘id’] のようにあたかも CakePHP の findAll メソッドを使ったように結果を取得できます。

これを実行すると、こんな感じ。

当然ですが、クエリ文は query メソッドで実行したものが使われています。left join でないので、データベース屋さんとしてはすっきりという感じですね。

私見ですが、そもそもO/Rマッピング(ORM)ってのは、オブジェクト指向とデータ指向を結びつけるというのが目的で、なにもクラス(オブジェクト)とテーブルを1対1にする、というのが目的ではないのですね(初手としては良いのでしょうが)。

O/R マッピングをするときに、右のデータは既に正規化されている状態なので、そのままクラスにマッピングするのは不便です。と言いますか、非正規化の状態に戻さないと使えません。RDB 自体がリレーショナルであることを基本としているので、ひとつの現実を表すのに publisher_id や state_id などの identify を使う訳で、これを id のままマッピングするよりも、元の非正規化の状態に戻して、オブジェクト指向の世界へと誘うのがよいかと思っています。

ま、機能と使い方は色々なので、利用する時に合わせるのがベターかと。

カテゴリー: CakePHP パーマリンク

独自のモデルを作ってマッピングする(queryを使う) への1件のコメント

  1. k1496 のコメント:

    勝手にモデルクラスにメソッドを追加して$this->query()してたけど、findAll()を
    オーバーライドすれば型がフレームワークの作法に則る訳か。。

    オブジェクト指向も途中で辞職したのでまだまだですね>自分

    あとは今外向けのサイトを担当しているのでSQLインジェクションとか
    調べてます。Prepared Statementがどうたらこうたら。。

コメントは停止中です。