Дерево категорий Laravel

Сидинг БД таблицы категорий

Мграции и сиддинг
create_categories_table
public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->integer('p_id');
            $table->timestamps();
        });
    }
seed.php
        Categories::factory()->create([
            'id' => 1,
            'name' => 'Главная',
            'p_id' => 0,
        ]);
        Categories::factory()->create([
                'id' => 2,
                'name' => 'Компьютеры',
                'p_id' => 1,
        ]);
        Categories::factory()->create([
                'id' => 3,
                'name' => 'Телефоны',
                'p_id' => 1,
        ]);
        Categories::factory()->create([
                'id' => 4,
                'name' => 'Планшеты',
                'p_id' => 1,
        ]);
        Categories::factory()->create([
                'id' => 5,
                'name' => 'Ноутбуки',
                'p_id' => 2,
        ]);
        Categories::factory()->create([
                'id' => 6,
                'name' => 'ПК',
                'p_id' => 2,
        ]);
        Categories::factory()->create([
                'id' => 7,
                'name' => 'Lenovo',
                'p_id' => 5,
        ]);
        Categories::factory()->create([
                'id' => 8,
                'name' => 'Apple',
                'p_id' => 5,
        ]);
        Categories::factory()->create([
                'id' => 9,
                'name' => 'Asus',
                'p_id' => 5,
        ]);
        Categories::factory()->create([
                'id' => 10,
                'name' => 'Acer',
                'p_id' => 5,
        ]);
        Categories::factory()->create([
                'id' => 11,
                'name' => 'Apple',
                'p_id' => 3,
        ]);
        Categories::factory()->create([
                'id' => 12,
                'name' => 'Samsung',
                'p_id' => 3,
        ]);
        Categories::factory()->create([
                'id' => 13,
                'name' => 'Nokia',
                'p_id' => 3,
        ]);
        Categories::factory()->create([
                'id' => 14,
                'name' => 'Huawei',
                'p_id' => 3,
        ]);
        Categories::factory()->create([
                'id' => 15,
                'name' => 'Xiaomi',
                'p_id' => 3,
        ]);
        Categories::factory()->create([
                'id' => 16,
                'name' => 'Apple',
                'p_id' => 4,
        ]);
        Categories::factory()->create([
                'id' => 17,
                'name' => 'Xiaomi',
                'p_id' => 4,
        ]);
        Categories::factory()->create([
                'id' => 18,
                'name' => 'Apple',
                'p_id' => 6,
        ]);

Модель category

Categories.php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Categories extends Model
{
    use HasFactory;

    public static function getCategories(){
        // Получаем одним запросом все разделы
        $arr = self::orderBy('name')->get();
        // Запускаем рекурсивную постройку дерева и отдаем на выдачу
        return self::buildTree($arr, 0);
    }

    public static function buildTree($arr, $pid = 0) {
        // Находим всех детей раздела
        $found = $arr->filter(function($item) use ($pid){return $item->p_id == $pid; });

        // Каждому детю запускаем поиск его детей и записываем в свойство sub
        foreach ($found as $key => $cat) {
            $sub = self::buildTree($arr, $cat->id);
            // То что ниже можно заменить на - $cat->sub = $sub;
            $found[$key]->sub = $sub;
        }

        return $found;
    }

    // Альтернативный вариант

    public static function getCategories2(){ // получаем корневые категории (у которых нету родителей) с жадной загрузкой отношения categories()
        return self::where('p_id','=',0)->with('categories')->get();
    }

    public function categories(){ // отношение категории к дочерним категориям, отдаётся сразу с жадной загрузкой
        return $this->hasMany(self::class,'p_id','id')->with('categories');
    }
}

Контроллер

TreeController.php
namespace App\Http\Controllers;

use App\Models\Categories;
use Illuminate\Http\Request;

class TreeController extends Controller
{
    public function index(){
        $categories = Categories::getCategories();
        return view('tree')->with(['categories' => $categories]);
    }

    public function index2(){
        $categories = Categories::getCategories2();
        return view('tree2')->with(['categories' => $categories]);
    }
}

Первый вариант

tree.blade.php
<ul>
    @foreach($categories as $category)
        <li>
            {{$category->name}}
            @if($category->sub->count())
                @include('tree', ['categories' => $category->sub])
            @endif
        </li>
    @endforeach
</ul>

Второй вариант (предпочтительный) через жадную загрузку отношений самих на себя

tree2.blade.php
<ul>
    @foreach($categories as $category)
        <li>
            {{$category->name}}
            @if($category->categories->isNotEmpty())
                @include('tree2',['categories' => $category->categories])
            @endif
        </li>
    @endforeach
</ul>

Last updated