Breathnote
Laravel + MySQL:隣接リストモデルのテーブルからレコードを取得する

Laravel + MySQL:隣接リストモデルのテーブルからレコードを取得する

再帰クエリをサポートしていないDBMSではアンチパターンとなるナイーブツリー。

今回は、ナイーブツリーの中でも扱いが面倒な「隣接リストモデル」のテーブルを用いて、子孫レコードを取得、Laravel Bladeを用いて階層表示してみます。

開発環境

  • PHP 7.3
  • Laravel 6.20
  • MySQL 5.7

テーブル定義

本記事では以下のdivisionsテーブルを使用します。idparent_idで部署の関係を表している階層構造のテーブルです。

idparent_idname
1NULL技術部
21開発チーム
31設計チーム
42クライアント班
52サーバ班
63研究設計班
73生産技術班
8NULL企画部
9NULL営業部

子孫レコードを取得してみる

技術部以下の部署を配列形式で取得します。

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を再帰的に呼び出すことで、ツリー構造のテーブル表示を実現しています。

所感

正直なところ、あまり使い道は無いと思います。