Laravel + MySQL:隣接リストモデルのテーブルからレコードを取得する
再帰クエリをサポートしていないDBMSではアンチパターンとなるナイーブツリー。
今回は、ナイーブツリーの中でも扱いが面倒な「隣接リストモデル」のテーブルを用いて、子孫レコードを取得、Laravel Bladeを用いて階層表示してみます。
開発環境
- PHP 7.3
- Laravel 6.20
- MySQL 5.7
テーブル定義
本記事では以下のdivisions
テーブルを使用します。id
とparent_id
で部署の関係を表している階層構造のテーブルです。
id | parent_id | name |
---|---|---|
1 | NULL | 技術部 |
2 | 1 | 開発チーム |
3 | 1 | 設計チーム |
4 | 2 | クライアント班 |
5 | 2 | サーバ班 |
6 | 3 | 研究設計班 |
7 | 3 | 生産技術班 |
8 | NULL | 企画部 |
9 | NULL | 営業部 |
子孫レコードを取得してみる
技術部以下の部署を配列形式で取得します。
Division.php
<?php
namespace App\Models;
class Division extends Model
{
protected $table = 'divisions';
protected $primaryKey = ['id'];
// 直下の部署を取得
public function childDivs()
{
return $this->hasMany('App\Models\Division', 'parent_id', 'id');
}
// 傘下の部署を再帰的に取得
public function allChildDivs()
{
return $this->childDivs()->with('allChildDivs');
}
// 指定した部署 + 傘下の部署を配列形式で取得
public static function getDivFamily(Division $model)
{
static $divs = [];
if ($model) {
$divs[] = $model;
foreach ($model->allChildDivs as $childDivs) {
getDivFamily($childDivs);
}
}
return $divs;
}
}
DivisionController.php
<?php
namespace App\Http\Controllers;
use App\Models\Division;
class DivisionController extends Controller
{
public function index(Request $request)
{
$div = Division::find('1');
$divFamily = Division::getDivFamily($div);
}
}
階層表示してみる
Laravel Blade
を利用して、傘下の部署を階層表示します。レイアウトにはTreeGrid jQuery pluginを使いました。導入方法は本題から逸れるので解説に含みません。
まず初めにコントローラを作りましょう。
DivisionController.php
<?php
namespace App\Http\Controllers;
use App\Models\Division;
class DivisionController extends Controller
{
public function index(Request $request)
{
$rootDivs = Division::whereNull('parent_id')->get();
return view('parent', [
'rootDivs' => $rootDivs ?? [],
]);
}
}
次に、親となるBladeファイルを作ります。
parent.blade.php
<table class="tree">
<thead>
<tr>
<th>部署ID</th>
<th>部署名</th>
</tr>
</thead>
<tbody>
@foreach ($rootDivs as $rootDiv)
<tr class="treegrid-{{ $rootDiv->id }}">
<td>{{ $rootDiv->id }}</td>
<td>{{ $rootDiv->name }}</td>
</tr>
@each('child', $rootDiv->allChildDivs, 'div')
@endforeach
</tbody>
</table>
最後に、子供となるBladeファイルを作ります。
child.blade.php
<tr class="treegrid-{{ $div->id }} treegrid-parent-{{ $div->parent_id }}">
<td>{{ $div->id }}</td>
<td>{{ $div->name }}</td>
</tr>
@each('child', $div->allChildDivs, 'div')
child.blade.php
を再帰的に呼び出すことで、ツリー構造のテーブル表示を実現しています。
所感
正直なところ、あまり使い道は無いと思います。