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

Миграция для БД
Простая схема для таблицы:
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.
Вот наши данные в базе данных:

Модель 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
Перевод: Алексей Широков
Наш Телеграм-канал — следите за новостями о Laravel.
