Многоуровневое меню Laravel

Тут был подробный вопрос о том как выводить меню из БД в header/footer и т.д., которые подключаются к основному шаблону через @inlude. Удалил из за ненадобности.
В комменте ниже, я описал, как я смог это сделать.


Ответы (2 шт):

Автор решения: Hi_TecH

Версия Laravel 8.x
В общем, перерыв весь инет, взяв некоторую инфу с разных источников в том числе и с доков, нашел не сложный и рабочий метод как вывести из БД многоуровневое меню. Объясню максимально подробно, вдруг будут смотреть новички.

Т.к. у меня меню, header и footer подключаются через @inlude сделать стандартным методом через контроллер, передав во вьху переменную, не получалось. Поэтому нашел на просторах сети, в том числе и доках способ как это сделать иначе. В laravel предусмотрен данный функционал, называется VIEW COMPOSERS (не путать с пакетным менеджером composer). Для начала нам нужно создать новый провайдер следующей командой:

php artisan make:provider ComposerServiceProvider

Добавим его сразу в масив providers, где нибудь в конце, который находится по адресу config\app.php:

    'providers' => [
         
         /*
         * Other Service Providers...
         */
        App\Providers\ComposerServiceProvider::class,
],

Далее есть 2 варианта, первый использовать чисто колбэки, второй это использовать класс. Подробно об обоих вариантах рассмотрено тут:

https://si-dev.com/ru/blog/laravel-view-composers.

Я использовал класс. Для этого нам нужно создать в директории App\Http папку ViewComposers и в ней файл NavigationComposer.php. Названия и локация файлов не так важна, как я понял, но я поступил как советовали авторы разных статей. В файле NavigationComposer.php мы будем прописывать выборку из БД для нашей меню. У меня она всего двухуровневая, поэтому выборка выглядит так:

    <?php

namespace App\Http\ViewComposers;

use Illuminate\View\View;
use App\Models\Menu;

class NavigationComposer
{
      public function compose(View $view)
      {
          $catalog = Menu::where('parent_id','=',NULL)->get();
          return $view->with('catalog', $catalog);
      }
}

Так же необходимо внести некоторые коррективы в ранее созданный нами ComposerServiceProvider. Он находится в app\Providers.И прописываем там:

    <?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use App\Models\Menu;

class ComposerServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        View::composer(
            'inc.catalog', 'App\Http\ViewComposers\NavigationComposer'
        );
    }
}

Я скинул общую структуру, но добавить вы должны только то,что находится внутри функции boot. Первый аргумент это наш шаблон меню (он у меня находится в resourses\views\inc\catalog.blade.php), а второй этот как раз наш навигатор, где мы делаем выборку. Так же естественно нам нужна модель и миграция. Создаем их:

php artisan make:model -m Menu.

Потом находим нашу таблицу в database\migrations. Если вы ее создали с тем же названием что и я, то она будет называться датасозданияcreate_menus_table.php. В ней достаточно прописать:

Schema::create('menus', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('parent_id')->unsigned()->nullable()->index();
        $table->string('name');
        $table->string('url');
    });

После запускаете миграцию php artisan migrate. И заполняете вашу табличку. В графе parent_id для категорий пропишите, например, NULL, а для подкатегорий id той категории, к которой данная подкатегория относится. Дальше открываем модель по адресу app\Models\Menu(либо ваша модель с другим названием). И прописываем зависимость:

    <?php

namespace App\Models;

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

class Menu extends Model
{
    use HasFactory;

    protected $table = 'menus';

        public function parent()
        {
            return $this->belongsTo('App\Models\Menu', 'parent_id');
        }
        public function children()
        {
            return $this->hasMany('App\Models\Menu', 'parent_id');
        }
}

И теперь осталось вывести эти данные в нашей вьюехе. Делал я следующим образом:

<div class='cssmenu'>
    <ul>
     <li class='has-sub'><a><span>Каталог</span></a>
      <ul>
        @foreach($catalog as $cat) <!-- Здесь выводятся категории-->
         <li class='has-sub'><a href="/{{ $cat->url }}"><img class="catalogimg" src="/img/jpg&png/categories/{{ $cat->img }}"><span>{{$cat->name}}</span></a>
          <ul>
            @foreach($cat->children as $subcat) <!-- Здесь подкатегории-->
             <li><a href="/{{ $subcat->url }}"><img class="catalogimg" src="/img/jpg&png/categories/{{ $subcat->img }}"><span>{{ $subcat->name }}</span></a></li>
            @endforeach
          </ul>
         </li>
         @endforeach
      </ul>
     </li>
    </ul>
  </div>

Я НЕ ЯВЛЯЮСЬ автором данных решений, инфу брал с разных сайтов и источников и выбрал то, что подошло мне (это для справки уточнил, на всякий случай). Надеюсь кому-то помог.

→ Ссылка
Автор решения: Евгений

Получем все элементы меню (разделы каталога)

$items = Category::all();

И передаем эту переменную в шаблон, скажем layout.part.tree (с помощью ComposerServiceProvider)

@if (count($items->where('parent_id', $parent)))
    <ul>
    @foreach ($items->where('parent_id', $parent) as $item)
        <li>{{ $item->name }}
        @include('layout.part.tree', ['parent' => $item->id])
        </li>
    @endforeach
    </ul>
@endif

Шаблон подключает себя рекурсивно, позволяя реализовать меню любого уровня вложенности. Причем, мы используем только один запрос к БД. И подключаем этот шаблон там, где нужно показать меню каталога:

<div id="catalog-tree">
    @include('layout.part.tree', ['parent' => 0])
</div>
→ Ссылка