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 のままマッピングするよりも、元の非正規化の状態に戻して、オブジェクト指向の世界へと誘うのがよいかと思っています。
ま、機能と使い方は色々なので、利用する時に合わせるのがベターかと。
勝手にモデルクラスにメソッドを追加して$this->query()してたけど、findAll()を
オーバーライドすれば型がフレームワークの作法に則る訳か。。
オブジェクト指向も途中で辞職したのでまだまだですね>自分
あとは今外向けのサイトを担当しているのでSQLインジェクションとか
調べてます。Prepared Statementがどうたらこうたら。。