Категории бесконечной вложенности при помощи рекурсивных отношений hasMany

Рекурсивные отношения hasMany с неограниченными подкатегориями

Довольно часто в интернет-магазинах можно увидеть множество уровней категорий и подкатегорий, вплоть до бесконечности. Эта статья покажет вам, как сделать это элегантно при помощи Laravel Eloquent.

Мы создадим мини-проект для просмотра подкатегорий в детском магазине, например, пять уровней:

Рекурсивные отношения hasMany с неограниченными подкатегориями

Миграция для БД

Простая схема для таблицы:

Schema::create('categories', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('name');
    $table->unsignedBigInteger('category_id')->nullable();
    $table->foreign('category_id')->references('id')->on('categories');
    $table->timestamps();
});

У нас просто есть поле с названием и связь с самой таблицей. Таким образом, у большинства родительских категорий будет category_id = NULL, а у других подкатегории — свой собственный parent_id.

Вот наши данные в базе данных:

Рекурсивные отношения hasMany с неограниченными подкатегориями

Модель Eloquent и отношения

Для начала, в app/Category.php мы добавляем простой метод hasMany() — у категории могут быть другие подкатегории:

class Category extends Model
{
    public function categories()
    {
        return $this->hasMany(Category::class);
    }
}

Теперь время главного «секрета» статьи . Знаете ли вы, что можно создавать рекурсивные отношения? Вот так:

public function childrenCategories()
{
    return $this->hasMany(Category::class)->with('categories');
}

Таким образом, если вы вызовете Category::with(‘categories’), это получите один «дочерний» уровень, а Category::with(‘childrenCategories’) выдаст вам столько уровней, сколько сможет найти.

Маршрут и метод контроллера

Теперь давайте попробуем показать все категории и подкатегории, как в примере выше.

В routes/web.php добавим:

Route::get('categories', 'CategoryController@index');

А в app/Http/CategoryController.php сделаем так:

public function index()
{
    $categories = Category::whereNull('category_id')
        ->with('childrenCategories')
        ->get();
    return view('categories', compact('categories'));
}

Как видите, мы загружаем только родительские категории, а дочерние как отношения. Всё просто.

Шаблон и рекурсивный суб-шаблон

Перейдем к структуре шаблонов. Так выглядит resources/views/categories.blade.php:

<ul>
    @foreach ($categories as $category)
        <li>{{ $category->name }}</li>
        <ul>
        @foreach ($category->childrenCategories as $childCategory)
            @include('child_category', ['child_category' => $childCategory])
        @endforeach
        </ul>
    @endforeach
</ul>

Сначала основные категории, а затем загружаем дочерние категории при помощи @include.

Самое приятное, что resources/views/admin/child_category.blade.php будет использовать рекурсивную загрузку самого себя. Посмотрите код:

<li>{{ $child_category->name }}</li>
@if ($child_category->categories)
    <ul>
        @foreach ($child_category->categories as $childCategory)
            @include('child_category', ['child_category' => $childCategory])
        @endforeach
    </ul>
@endif

Как видите, внутри child_category.blade.php у нас есть @include(‘child_category’), поэтому шаблон рекурсивно загружает дочерние элементы, пока есть категории внутри текущей дочерней категории.

Вот и всё! Теперь у нас есть неограниченный уровень подкатегорий — в базе данных, в отношениях Eloquent и в шаблонах.

 

Автор: Povilas Korop
Перевод: Demiurge Ash