k01ken’s b10g

He110 W0r1d!

CakePHP3で自己結合を行う方法

開発環境は、Windows 10 Pro(64bit) + PHP 7.2.12 + CakePHP 3.7.9。

今回は、postsテーブルを自己結合してみます。ネットを探しても、自分の意図にあった記事がなかったので書いてみることにしました。以下はpostsテーブルの中身。

postsテーブル

create table posts(
id int primary key auto_increment,
post text not null,
quote_id int,
created datetime,
modified datetime,
foreign key fk_quote_id(quote_id) references on posts(id);
)charset=utf8mb4;

やりたいことは、postsテーブルのquote_idに値がある場合に、そのquote_idと同じidのデータを1対1の自己結合したいと思います。

まずは、/src/Model/Table/PostsTable.php内のinitializeメソッド内に、以下のコードを書きます。

<?php
  $this->hasOne('QuotePosts', [
    'className' => 'Posts',
    'foreignKey' => 'id',
    'bindingKey' => 'quote_id',
    'joinType' => 'INNER'
  ]);
?>

今度は、コントローラやモデルで、データを読み込む場合、

<?php
  $query = $this->Posts->find()->where(['Posts.quote_id IS NOT' => NULL]);
  $query->contain(['QuotePosts']);
?>

もし、データが取得できれば、quote_post内にデータが入っています。
実は、INNER JOINの場合は、内部結合の仕様により、上記のwhere句の条件を記述しなくても、NULLじゃない値しか取得できません。例えば、Postsで取得した際に、quote_idがNULLの場合は、取得せずに、NULLじゃない場合は取得したければ、上記のhasOneメソッドのjoinTypeをINNERからLEFTにします。

もし、selectメソッドを利用して、Posts,QuotePostsそれぞれで取得したい、カラムを制限したければ、コントローラー内などのフィールド部分に以下のように書いておく。

<?php

  private $posts_columns = [
    "Posts.id", "Posts.post", "Posts.quote_id"
  ];

  private $quoteposts_columns = [
    "QuotePosts.id", "QuotePosts.post", "QuotePosts.quote_id"
  ];

?>

そして、使用する際には、

<?php
  $query = $this->Posts->find()->select($this->posts_columns);
  $query->contain(['QuotePosts' => function($q){
     return $q->select($this->quoteposts_columns);
  }]);
?>

のように使用する。

■参考リンク
https://book.cakephp.org/3/ja/orm/associations.html