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

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 のままマッピングするよりも、元の非正規化の状態に戻して、オブジェクト指向の世界へと誘うのがよいかと思っています。

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

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

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

  1. k1496 のコメント:

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

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

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

コメントは停止中です。