CakePHP2でfind時、Containableとrecursiveどちらを使うのが良いか?

Bake-with-CakePHP Logo

CakePHP2で複数テーブルをJOINしながらfindする際のプログラミングとして、Containbleとrecursiveのどちらを使うかに関するTipsを残しておきます。

前提条件

ブログ

ブログ記事を複数人のライターで投稿できる、CakePHP2でできたアプリケーションがあったとします。データベース構成はこんな感じ:

ブログデータベース構成例

 

  • admins:
    • システム管理者。ライター管理や記事管理等のあらゆる権限を有する。
  • writers:
    • ブログ記事のライター。ブログ管理画面にログインし、記事を書くことができる。
  • articles:
    • 記事。大き分けてタイトル・導入文・コンテンツ文章の3つで構成される。どのライターが書いたかが分かるよう、writer_idを有する。
  • uploaded_images:
    • 記事に挿入する画像。各記事はアイキャッチ画像だけ、articlesテーブルのuploaded_image_idで指定する仕様になっている。
  • categories:
    • 記事カテゴリー。記事は複数カテゴリーに所属する可能性を考慮し、多対多の構成(articles_categoriesテーブル)をとっている。
  • tags:
    • 記事に付けられるタグ。記事は複数タグを付けられるよう、多対多の構成(articles_tagsテーブル)をとっている。
  • comments:
    • ブログ訪問者が記事に投稿できるコメント。showカラムはデフォルト値が0(非表示)で、ライターもしくはシステム管理者が承認(0→1)することで、初めてブログ上に表示されるようになる。

やりたいこと:「articles.id=21の記事情報と、その記事を書いたライター情報だけをfind()したい」

Modelの記述は

であると仮定します。

この状態でController内に記述するとしたら、以下2通りあります。

1.recursiveを指定してfind()

2.Containable Behaviorを利用してfind()

1.2.どちらでも記事情報+ライター情報を取得することが可能です。

Containable Behaviorのほうが使いやすい

結論を言うと、Containable Behaviorでfind時のアソシエーションをコントロールするほうが便利です。理由はrecursiveの特性にあります。

recursiveは、値によってどこまでのアソシエーション階層のtableをJOINさせるかを決めるパラメータである

たとえば、

  • recursive=-1:
    • 自分のtableからのみSELECT
  • recursive=0:
    • 自分のtable + belongsToのtableをJOINしてSELECT
  • recursive=1:
    • 自分のtable + belongsToのtable + hasManyのtableをJOINしてSELECT

…といった具合に、値が大きくなるほど多数の階層のtableをJOINの対象にしていくのです。

つまり、

階層単位でJOINするテーブルをコントロールするため、場合によってはあなたが必要としないテーブルまでJOINさせてしまうことがある

というわけです。今回の要件を改めて見てみると、

「articles.id=21の記事情報と、その記事を書いたライター情報だけをfind()したい」

となっています。1.のrecursiveを0に指定してfind()をすると、writersだけでなくuploaded_imagesまでJOINしてしまうことになります。SELECT文発行時に不要なtableをJOINしていることになるので、

DBへの負担とPHPのメモリ使用量を無駄に消費している

ことになってしまいます。

Containable BehaviorはJOINさせるtableを直接指定することが可能

2.では事前に「Containable Behaviorを使いますよ〜」という宣言をするため、

上記のようにload()をした後にcontainが使えるようになります。Containable Behaviorの詳細な使い方は公式ドキュメントに譲るので、確認していただければと。

recursiveを使うのをやめて、Containable Behavior一択でいこう

このContainable Behaviorは、データベース内のテーブル数が少なかろうと多かろうと関係なく、recursiveに比べて使い勝手が圧倒的に良いです。

そのため、もしCakePHP2で0からアプリケーションを構築しようと思っているのであれば、AppModel内に

と入れておくほうが良いと考えています。こうすることで、Controller内で都度load()を呼び出す必要が無くなるからです。

もし既存アプリケーションで、且つ複数開発者によってcontainとrecursiveの利用が統一されていなかったら

containを使用する方向に寄せて、recursiveを使用している箇所をcontainの記述に修正していくことをオススメします。

完了したら、AppModel内で

と記述し、根本的にrecursiveを使っていないことを明示しておきましょう(CakePHP2の公式ドキュメントでも推奨されています)。

また、社内コーディング規約として

「find()等でJOIN時はrecursiveではなく、containを使うこと」

というルールを作り、エンジニアチーム内に明示しましょう。ルールにしておけば、仮に外部企業に機能改修を依頼することがあった時、予めルールを先方に共有しておくことで品質担保に繋がります。