CakePHPの場合、モデルとテーブルが1対1と想定しているので、これ以外のマッピングをする場合は、独自の Model を作ったほうがいいと思います。
例えば、先の記事のように、出版社(Publisher)から必ず都道府県(State)の名前を参照するのであれば、あらかじめそういう Model を作ってしまったほうが利便性が高いかな、と。
このような関係があるとき、雑誌の一覧を次のように取得したいと思う訳です。
- 雑誌ID
- 雑誌名
- 出版社名
- 出版社の所在地/都道府県
これを通常ならば、データベースのビューを作って、
- id
- magazine_name
- publisher_name
- publisher_state
のような形で取得しても良いのですが、環境によってはデータベースを変更できなかったり、データ管理は別の会社が構築したものだったりして、データベースとプログラムの間には大きな溝があるのが常だったりします。
# 小さなプロジェクトならばいいんですけどね。
# あと、もの分かり良い(以下略
という訳で http://servername/magazines/lists でアクセスできるようにします。
まずは、ビューを作成して、普通のモデルを使っているように書きます。というか書きたいわけです。
<!-- 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 メソッドを追加します。
<?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)になります。
<!-- 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 のままマッピングするよりも、元の非正規化の状態に戻して、オブジェクト指向の世界へと誘うのがよいかと思っています。
ま、機能と使い方は色々なので、利用する時に合わせるのがベターかと。
勝手にモデルクラスにメソッドを追加して$this->query()してたけど、findAll()を
オーバーライドすれば型がフレームワークの作法に則る訳か。。
オブジェクト指向も途中で辞職したのでまだまだですね>自分
あとは今外向けのサイトを担当しているのでSQLインジェクションとか
調べてます。Prepared Statementがどうたらこうたら。。