【CakePHP2/CakePHP3】データをスレッド形式で取得する。スレッド形式に変換する。
fukasawaです。こんにちは。じめじめしますね。
さて、バシャログにコメント機能はありませんが、一般的なブログではコメントをスレッド形式で投稿できたりします。そのようなコメント機能を実装する場合、DBにコメント用のテーブルを作成することになりますが、再帰的なリレーションを作成し、データを取得する際は自己結合することによってスレッド形式でデータを取得することができます。今回は、CakePHPでそのようなデータを取得する方法について見ていきたいと思います。
- CakePHP2.7.9、CakePHP3.2.5で検証しています。
- Tree ビヘイビアについてはこちらの記事では扱いません。
準備
テスト用のテーブルを用意しました。ブログに対するコメントを格納するコメントテーブルです。
テストデータも用意しました。入っているのは「ブログエントリーID(post_id) = 5 に対するコメント」という想定のデータです。「comment_parent_id」には各コメントの親となるコメントのIDが格納されており、これを結びつけていくとスレッド形式のデータが取得できるという感じです。
このテストデータを、以下のようなスレッド形式で取得します。
find('threaded')を使って取得する
データを取得する際、find()のタイプに'threaded'を指定すると、今回のようなスレッド形式のデータをそのままズルっと取得することができます。
サンプルでgetCommentThread()というメソッドを作成してみました。引数に$post_id(ブログエントリのID)を渡すと、紐づくコメントをネストされた配列として取得するメソッドです。CaekePHP2とCakePHP3で書き方が異なるようなのでそれぞれ見てみます。
CakePHP2の場合
Comment.php (モデル)
public function getCommentThread($post_id)
{
if (empty($post_id)) {
return false;
}
// コメントをスレッド形式で取得。
$result = $this->find('threaded',[
'fields'=> [
// 「fields」オプションを指定するときは「parent」オプションに指定したフィールドを指定する必要がある。
'id','title','body','user_name','comment_parent_id'
],
'conditions' => [
'post_id' => $post_id
],
'parent' => 'comment_parent_id' // 親になるコメントIDが格納されているフィールドを指定。
]);
return $result;
}
- 「parent」オプションで親のコメントIDが格納されるフィールドを指定します。指定しなかった場合はデフォルトで'parent_id'が指定されます。
- 「fields」オプションで、取得するフィールドを指定する場合は「parent」オプションで指定したフィールドを含める必要があります。(含めないとうまく動かないみたいです。)
試しに、適当なコントローラで出力してみます。
public $uses = ['Comment'];
public function index()
{
debug($this->Comment->getCommentThread(5));
}
スレッド形式の配列が取得できていることを確認できると思います。
CakePHP3の場合
CommentsTable.php(テーブルクラス)
public function getCommentThread($post_id) {
if (empty($post_id)) {
return false;
}
$result = $this->find('threaded', [
'fields' => [
// 「fields」オプションを指定するときは「parentField」オプションに指定したカラムを入れる必要がある
'id', 'title', 'body', 'user_name', 'comment_parent_id'
],
'parentField' => 'comment_parent_id',
'conditions' => ['post_id' => $post_id]
])->hydrate(false)->toArray(); // find()の結果を配列で取得
return $result;
}
根本的なところでCakePHP2とはfind()の書き方が異なる…、というのもあるのですが、他にもオプションのキー名が異なっています。
- 「parentField」オプションで親のコメントIDが格納されるカラムを指定します。指定しなかった場合はデフォルトで'parent_id'が指定されます。
- 「fields」オプションで、取得するフィールドを指定する場合は「parentField」オプションで指定したフィールドを含める必要があります。(含めないとうまく動かないみたいです。)
適当なコントローラで出力してみます。
public function index()
{
$this->Comments = TableRegistry::get('Comments');
$comments = $this->Comments->getCommentThread(5);
debug($comments);
}
Hash::nest()
例えば、DBからスレッド形式でデータを取得したいわけではなく、既にある配列をスレッド形式にフォーマットしたい場合はHash::nest()を使用します。
まずfind()でコメントデータを取得し、取得したデータを後からHash::nest()を使用してスレッド形式に変換してみます。
CakePHP2の場合
任意のコントローラ
public $uses = ['Comment'];
public function index()
{
$post_id = 5; //ブログエントリのID
// コメント取得
$comments = $this->Comment->find('all', [
'fields' => [
'id', 'title', 'body', 'user_name', 'comment_parent_id'
],
'conditions' => [
'post_id' => $post_id
],
]);
// スレッド形式に変換
$comments = Hash::nest($comments, [
'idPath' => '{n}.Comment.id', // コメントのキーを示すパス
'parentPath' => '{n}.Comment.comment_parent_id', // 親となるコメントのIDが格納されているキーを示すパス
]);
// 出力
debug($comments);
}
- 'idPath': コメントのキーを示すパス
- 'parentPath': 親となるコメントのIDが格納されているキーを示すパス
※パスの指定は「Hash パス構文」に従います。
スレッド形式の配列に変換されていることを確認できると思います。
CakePHP3の場合
※Hash::nestの使い方は、基本的にCakePHP2と同じです。
任意のコントローラ
use Cake\Utility\Hash;
public function index()
{
$post_id = 5; //ブログエントリのID
// コメント取得
$this->Comments = TableRegistry::get('Comments');
$comments = $this->Comments->find('all', [
'fields' => [
'id', 'title', 'body', 'user_name', 'comment_parent_id'
],
'conditions' => [
'post_id' => $post_id
],
])->hydrate(false)->toArray(); // find()の結果を配列で取得
// スレッド形式に変換
$comments = Hash::nest($comments, [
'idPath' => '{n}.id',
'parentPath' => '{n}.comment_parent_id',
]);
// 出力
debug($comments);
}
- 'idPath': コメントのキーを示すパス
- 'parentPath': 親となるコメントのIDが格納されているキーを示すパス
※パスの指定は「Hash パス構文」に従います。
CakePHP3の場合もCakePHP2とオプションは同じですが、CakePHP3の場合find()の結果がEntityとして取得されます。Hashは配列を扱うための機能なので、CakePHP2と同じようにfind()の結果に対しHash::nest()を使用する場合は、hydrate(false)を指定しfind()の結果を配列として取得する必要があります。