既存の Laravel プロジェクトに Closure Table を導入する
最初からツリー構造のデータを扱うことがわかっていれば、 franzose/closure-table を導入して、こちらの記事(
Laravel|Closure Tableで階層の深さが動的なカテゴリ構造を扱う - わくわくBank )などを参考にしながら設定すればよいのですが、後からコメントを階層化したいみたいなオーダーが出てきたときにどうすればよいか、というのが今回の記事のお話です。
1. 必要なライブラリをインストール
$ composer require franzose/closure-table
2. マイグレーションファイルの作成
Closure Table 用のテーブルを作成するとともに、元々のモデル用のテーブルに必要なカラムを追加します
(以下では、 comments テーブルをツリー化することを想定しています)
Closure Table 用のテーブルを作成
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateCommentClosuresTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('comment_closures', function (Blueprint $table) { $table->increments('closure_id'); $table->integer('ancestor', false, true); $table->integer('descendant', false, true); $table->integer('depth', false, true); $table->foreign('ancestor') ->references('id') ->on('comments') ->onDelete('cascade'); $table->foreign('descendant') ->references('id') ->on('comments') ->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('comment_closures'); } }
既存の comments テーブルにカラムを追加
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class AddClosureColumnsOnComments extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('comments', function (Blueprint $table) { $table->integer('parent_id')->unsigned()->nullable(); $table->integer('position', false, true); $table->integer('real_depth', false, true); $table->foreign('parent_id') ->references('id') ->on('comments') ->onDelete('set null'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('comments', function (Blueprint $table) { $table->dropColumn('parent_id'); $table->dropColumn('position'); $table->dropColumn('real_depth'); }); } }
3. モデルの作成
既存のモデル(Comment)を修正するほか、Closure Table 用のモデルを 3 つ追加します
Comment.php の修正
<?php namespace App; use Franzose\ClosureTable\Models\Entity; class Comment extends Entity implements CommentInterface { /** * The table associated with the model. * * @var string */ protected $table = 'comments'; /** * ClosureTable model instance. * * @var CommentClosure */ protected $closure = 'App\CommentClosure'; //複数代入を許可する項目 protected $fillable = ['name', 'content']; (以下略) }
use を追加するのと、$closure を追加するのが主な変更ですが、1点注意しなければならないのが複数代入の設定です。
ご存知のように、モデルには fillable か guarded のどちらかの属性を設定する必要がありますが
Closure Table を利用するためには、fillable で設定する必要があります。
というのも、Closure Table で利用するカラムに値を追加できるように
$this->fillable(array_merge($this->getFillable(), [$position, $depth]));
という処理を行っているためです。新規作成の場合にはこのあたり気にする必要はありませんが、既存のモデルを使う場合、注意が必要です。
CommentClosure.php の作成
<?php namespace App; use Franzose\ClosureTable\Models\ClosureTable; class CommentClosure extends ClosureTable implements CommentClosureInterface { /** * The table associated with the model. * * @var string */ protected $table = 'comment_closures'; }
CommentClosureInterface.php の作成
<?php namespace App; use Franzose\ClosureTable\Contracts\ClosureTableInterface; interface CommentClosureInterface extends ClosureTableInterface { }
CommentInterface.php の作成
<?php namespace App; use Franzose\ClosureTable\Contracts\EntityInterface; interface CommentInterface extends EntityInterface { }
4. 初期データの投入
新規作成の場合は必要ないのですが、既存データを利用する場合、comments.position に値を入れるとともに、comment_closures テーブルに初期データを投入する必要があります。
comments.position は同じ階層にあるデータの並び順を表すデータで、他のデータと重複しないように設定する必要があります。
また、comment_closure テーブルのほうには、いったんすべてのデータが最上位の階層に並んでいるようなデータを投入します。
手っ取り早いのは、プライマリキーに設定されている comments.id を使う方法で
update comments set position = id; insert into comment_closures (`ancestor`, `descendant`, depth) select id, id, 0 from comments;
こんな感じで設定しておけば大丈夫です
これで Closure Table が使えるようになると思います。それ専用に作られているだけあって、データの操作や取得はかなり便利に行うことができます。お試しください!