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がどうたらこうたら。。