まえおきなど
私の記事のまえおき
関連技術のバージョン:CakePHP3.3
アソシエーションしてますか?
※2次利用のための表記
ブラックジャックによろしく / 著作者名: 佐藤秀峰 / サイト: 漫画 on web
※アソシエーションの詳細については下記を参照してください。
アソシエーション - モデル同士を繋ぐ(公式)
今日は、CakePHP3のアソシエーションを支える、強力なORM機能の事例をご紹介。
アソシエーションへの拒絶反応
見知らぬフレームワークを使う上で足かせとなるのが、フレームワークの学習コストである。
強力なORM機能を持つフレームワークは、通常のSQLで実装すれば3分で事足りるところを、慣れない人が実装すると10倍以上かかることもある。
上記のような状況から、せっかくのORM機能を利用せずにSQLを直接実行するような現場も多く見てきた。
かくいう私も、プロジェクトでいきなり見知らぬフレームワークを使うことを余儀なくされれば、おそらく納期を優先してSQLを直接実行するシーンも増えるだろうと思う。
「私の記事のまえおき」でも紹介している個人プロジェクトでは、納期など無いので思う存分アソシエーショナブルなシステム開発をしている。
そんなプロジェクトで、感じたメリットを1つ事例としてご紹介したい。
プロトタイプ開発の罠
プロトタイプ開発をしていると、データ設計が後付けになりがちとなってしまう。
(少なくとも私はそうなることがある)
今回はそんなプロトタイプ開発をしていた際の事例である。
status・・・ってなんだっけ?
プロトタイプ開発も順調に進み、大方の機能の[CRUD]のうちのD以外が実装された。
私の場合、DELETEに関する機能を後回しにする習慣がある。
ふと、show full columns form hoges; を実行しふとstatusなる情報が目に付く。
status (略) ステータス 1: 有効 2: Youtube上で非表示 3: このサービス上で削除
オーマイガッ!!参照時にstatus見てねーよ・・・しかもこのデータ一番参照されるデータだよ・・・
余談だが、このサイトでは動画の説明などを取り込んでいるため、Youtube側で非表示となったことを検知した場合、極力表に出ないように設計するべきだと考える。
対応案
前述のような場合の対応案を、下記2パターンで検証してみたい。
- SQL直接実行による実装時
- ORM機能を利用した実装時
そもそも、statusなんていうREAD時の大前提となる条件の実装を忘れるなよ!という突っ込みはいったん忘れてくれると大変ありがたい。
前提
修正箇所:20か所
試験の方法:手動による機能テスト
⇒SQLをべた書きするようなプロジェクトではPHPUnitは利用しないだろうという予想から
SQL直接実行による実装時
修正プロセス
べた書きのSQLに対して、そもそもの大前提となるstatusのような条件を加える場合、一つ一つ修正するしか方法がない。
この場合、下記のようなプロセスとなる。
- 対象となるテーブル名をグレップ検索
- ヒットしたSQLを一つ一つ修正
- 修正した機能を一つ一つテスト
修正コスト
- グレップ:1回3分
- SQLの修正:1か所30秒
- 動作試験:1か所4分
合計:93分
SQLの修正に30秒もかからないだろうとは思うが、20か所の修正コストの見積もりとして93分はまぁまぁ妥当だろう。
ORM機能を利用した実装
ORM機能を利用した実装の場合、効率的な対応案としては下記2つがあげられる。
(そのほかにも案をお持ちの方はお教えいただけると幸いです)
- アソシエーションの定義による修正
- query()のオーバーライドによる修正
今回私がとった修正方法はquery()のオーバーライドである。
・アソシエーションの定義による修正
今回の対応では採用していないため、ドキュメントの紹介だけしておく。
先にもリンクを張ったが「アソシエーション - モデル同士を繋ぐ」このページを”condition”で検索すれば、アソシエーションの定義でフィルタすることができる。
大変便利な機能ではあるが、一つ一つアソシエーションを直すのが面倒今後の設定漏れなどのことも考え採用しなかった。
先ほどの余談でもふれたが「極力表に出ない設計」とした場合、毎回のアソシエーションの定義に依存すべきではないと判断した。
・query()のオーバーライドによる修正
ブログになれていないため、長々と書いてしまった・・・
この方法のプロセスとコストは下記のとおり。
修正プロセス
- Tableクラスの修正
- 1機能を代表して試験
修正コスト
- メソッドのオーバーライド:1分
- 動作試験:4分
合計:5分
実に、役19分の1の修正コストである。
試験を1機能代表でよいかどうかは議論が分かれると思うが
- find()系メソッドはquery()が実行されること
- アソシエーション適用時にはquery()が実行されること
から1機能を代表すればよいと判断。
実際のソース
実際に実装したコードは下記のとおり。
管理画面などで、statusが1以外の情報を取得することも考慮して、$__isAllによってすべての情報にもアクセスできるようにしている。
public function query($type = 'all', $options = []) {
$query = parent::query();
if ($this->__isAll) {
return $query;
}
$query->where([
$this->alias() . '.status' => self::STATUS_ENABLE
]);
return $query;
}
まとめ
SQLの直接実行が消して悪いとは思わない。
パフォーマンスを優先する場合など、とても有効だと思う。
以前、「フレームワークのORMのメリットって何なの?」という問いに答えられなかったことがある。
今後はこのような問いに対して一つの答えを用意できて、非常に有意義な事例が得られたと思う。