Модульная архитектура — один из лучших способов поддерживать чистоту и расширяемость кода в больших Laravel-приложениях. Однако с ростом числа модулей часто возникает задача динамического построения пользовательского интерфейса, особенно навигационного меню, которое должно собирать пункты из разных частей системы. В этой статье я покажу элегантный подход к решению этой проблемы с помощью кастомных событий и View composer.
Проблема
В монолитном приложении меню можно просто прописать в шаблоне Blade. Но в модульной системе каждый модуль должен иметь возможность добавить свои пункты в меню, не нарушая работу других модулей. Жёсткая привязка к шаблонам приводит к хрупкости и усложнению поддержки.
Решение: событийный подход
Идея проста: мы создаём специальное событие BuildingMenu, которое собирает пункты меню от всех заинтересованных модулей. Затем через View composer передаём собранное меню в шаблон.
Шаг 1: Класс события BuildingMenu
<?php
namespace App\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class BuildingMenu
{
use Dispatchable, SerializesModels;
private array $items = [];
public function addItem(array $item): void
{
$this->items[] = $item;
}
public function getItems(): array
{
return $this->items;
}
}Класс выполняет две функции:
- Является событием (через трейты
Dispatchable,SerializesModels). - Служит контейнером для пунктов меню (методы
addItemиgetItems).
Обратите внимание: массив $items инициализирован как пустой, чтобы избежать ошибок при отсутствии пунктов.
Шаг 2: View composer для передачи меню в шаблон
<?php
use App\Events\BuildingMenu;
use Illuminate\Support\Facades\View;
View::composer('welcome', function ($view) {
$buildingMenu = new BuildingMenu();
event($buildingMenu);
$view->with('buildingMenu', $buildingMenu);
});View composer связывается с шаблоном welcome (или любым другим). При каждом рендеринге этого шаблона:
- Создаётся экземпляр
BuildingMenu. - Запускается событие
event($buildingMenu), которое уведомляет всех слушателей. - Объект меню передаётся в шаблон как переменная
$buildingMenu.
Шаг 3: Слушатель события, добавляющий пункты
<?php
use App\Events\BuildingMenu;
use Illuminate\Support\Facades\Event;
Event::listen(BuildingMenu::class, function (BuildingMenu $event) {
$event->addItem([
'text' => 'Счета',
'url' => 'invoices.index',
'icon' => 'fas fa-file-invoice-dollar',
'can' => 'view_invoices', // Проверка прав через Gate
]);
});Это пример слушателя, который может быть размещён в сервис-провайдере модуля. Он добавляет пункт «Счета» с иконкой, маршрутом и проверкой прав (ключ can позволяет скрыть пункт у пользователей без соответствующего разрешения).
Как это работает в модульной системе
Каждый модуль регистрирует своего слушателя в своём сервис-провайдере:
<?php
namespace Modules\Invoices\Providers;
use Illuminate\Support\ServiceProvider;
use App\Events\BuildingMenu;
use Illuminate\Support\Facades\Gate;
class InvoicesServiceProvider extends ServiceProvider
{
public function boot(): void
{
Event::listen(BuildingMenu::class, function (BuildingMenu $event) {
if (Gate::allows('view_invoices')) {
$event->addItem([
'text' => 'Счета',
'url' => 'invoices.index',
'icon' => 'fas fa-file-invoice-dollar',
]);
}
});
}
}Таким образом, модуль ничего не знает о других модулях, но вносит свой вклад в общее меню. При добавлении нового модуля нужно лишь зарегистрировать его провайдер в config/app.php — меню автоматически пополнится.
Расширение функциональности
Класс BuildingMenu можно улучшить, добавив:
- Группировку пунктов (например, выпадающие подменю).
- Сортировку по приоритету.
- Кэширование собранного меню (чтобы не строить его при каждом запросе).
Пример с группировкой:
<?php
$event->addItem([
'text' => 'Финансы',
'icon' => 'fas fa-wallet',
'submenu' => [
['text' => 'Счета', 'url' => 'invoices.index'],
['text' => 'Платежи', 'url' => 'payments.index'],
],
]);В шаблоне Blade можно реализовать рекурсивный вывод для поддержки многоуровневого меню.
Достоинства подхода
- Модульность: каждый модуль управляет своими пунктами независимо.
- Тестируемость: легко писать тесты для слушателей событий.
- Гибкость: пункты могут зависеть от прав пользователя, конфигурации, состояния приложения.
- Чистота шаблонов: в Blade остаётся только простой цикл по пунктам, без логики.
Недостатки
- Небольшое увеличение сложности (нужно создать событие и View composer).
- Требуется внимательно следить за производительностью, если пунктов очень много (можно добавить кэширование).
Заключение
Использование событий для построения меню превращает навигацию из статического элемента в динамическую, расширяемую систему. Такой подход особенно полезен в больших приложениях с командной разработкой, где несколько программистов работают над разными модулями одновременно. Он снижает связанность (coupling) и повышает связность (cohesion) кода, следуя принципам SOLID.
Внедрение описанного механизма займёт всего несколько часов, но сэкономит много времени в будущем при добавлении новых модулей и изменении структуры меню. Laravel с его мощной системой событий и сервис-контейнером идеально подходит для реализации подобных архитектурных паттернов.