API маршрутизатора
API маршрутизатора Flute CMS построен на базе Symfony Routing с дополнительными возможностями. Поддерживается как атрибутная маршрутизация, так и программное создание маршрутов.
Получение роутера
<?php
use Flute\Core\Router\Router;
// Через DI контейнер
$router = app(Router::class);
// Через хелпер
$router = router();Создание маршрутов
Базовые HTTP методы
<?php
// GET запрос
router()->get('/users', [UserController::class, 'index']);
// POST запрос
router()->post('/users', [UserController::class, 'store']);
// PUT запрос
router()->put('/users/{id}', [UserController::class, 'update']);
// DELETE запрос
router()->delete('/users/{id}', [UserController::class, 'destroy']);Дополнительные методы
<?php
// Множественные HTTP методы
router()->match(['GET', 'POST'], '/contact', [ContactController::class, 'handle']);
// Любой HTTP метод
router()->any('/webhook', [WebhookController::class, 'handle']);
// Статические страницы (возвращает view)
router()->view('/about', 'pages.about', ['title' => 'О нас']);
// Редирект
router()->redirect('/old-url', '/new-url', 301);Именованные маршруты
<?php
router()->get('/news', [NewsController::class, 'index'])
->name('news.index');
router()->get('/news/{id}', [NewsController::class, 'show'])
->name('news.show');
// Использование имени для генерации URL
$url = route('news.show', ['id' => 123]);
// Результат: /news/123Middleware
<?php
// Один middleware
router()->post('/news', [NewsController::class, 'store'])
->middleware('auth')
->name('news.store');
// Несколько middleware
router()->put('/news/{id}', [NewsController::class, 'update'])
->middleware(['auth', 'csrf', 'can:edit_news'])
->name('news.update');Группы маршрутов
<?php
// Группа с общим префиксом и middleware
router()->group(['prefix' => '/admin', 'middleware' => ['auth', 'admin']], function () {
router()->get('/news', [AdminNewsController::class, 'index']);
router()->post('/news', [AdminNewsController::class, 'store']);
router()->get('/news/{id}', [AdminNewsController::class, 'edit']);
});
// Вложенные группы
router()->group(['prefix' => '/api'], function () {
router()->group(['prefix' => '/v1', 'middleware' => 'throttle:100,1'], function () {
router()->get('/users', [ApiUserController::class, 'index']);
router()->get('/users/{id}', [ApiUserController::class, 'show']);
});
});Атрибутная маршрутизация
Автоматическая загрузка
Маршруты из атрибутов загружаются автоматически при вызове bootstrapModule() или loadRouterAttributes() в провайдере:
<?php
// В провайдере
public function boot(\DI\Container $container): void
{
$this->bootstrapModule(); // Включает loadRouterAttributes()
}Ручная регистрация
<?php
// Регистрация из директории
$router->registerAttributeRoutes(
[app_path('Modules/News/Controllers')],
'Flute\\Modules\\News\\Controllers'
);
// Регистрация из конкретного класса
$router->registerAttributeRoutesFromClass(NewsController::class);Встроенные middleware
Алиасы
| Алиас | Класс | Описание |
|---|---|---|
auth | IsAuthenticatedMiddleware | Проверка аутентификации |
guest | IsGuestMiddleware | Только для гостей |
can | CanMiddleware | Проверка разрешений |
csrf | CsrfMiddleware | CSRF защита |
htmx | HtmxMiddleware | HTMX запросы |
throttle | RateLimiterMiddleware | Ограничение частоты |
token | TokenMiddleware | API токены |
ban.check | BanCheckMiddleware | Проверка блокировки |
maintenance | MaintenanceMiddleware | Режим обслуживания |
Группы
<?php
// Встроенные группы middleware
'web' => ['csrf', 'throttle'],
'api' => ['throttle', 'ban.check'],
'default' => ['ban.check', 'throttle', 'maintenance'],Группа default добавляется ко всем маршрутам автоматически.
Middleware с параметрами
<?php
// Проверка разрешения
router()->get('/admin/users', [AdminController::class, 'users'])
->middleware('can:admin.users');
// Ограничение частоты: 100 запросов в минуту
router()->get('/api/data', [ApiController::class, 'data'])
->middleware('throttle:100,1');Регистрация своих middleware
Регистрация алиаса
<?php
// Регистрация своего middleware
router()->aliasMiddleware('blog.owner', BlogOwnerMiddleware::class);
// Использование
router()->put('/blog/{id}', [BlogController::class, 'update'])
->middleware('blog.owner');Регистрация группы
<?php
router()->middlewareGroup('blog', [
'auth',
'blog.owner',
'throttle:10,1'
]);
// Использование группы
router()->group(['middleware' => 'blog'], function () {
router()->put('/blog/{id}', [BlogController::class, 'update']);
router()->delete('/blog/{id}', [BlogController::class, 'destroy']);
});Создание middleware
<?php
namespace Flute\Modules\Blog\Middlewares;
use Closure;
use Flute\Core\Router\Contracts\MiddlewareInterface;
use Flute\Core\Support\FluteRequest;
use Symfony\Component\HttpFoundation\Response;
class BlogOwnerMiddleware implements MiddlewareInterface
{
/**
* Обработка запроса
*
* @param FluteRequest $request Текущий запрос
* @param Closure $next Следующий обработчик
* @param mixed ...$args Дополнительные аргументы
*/
public function handle(FluteRequest $request, Closure $next, ...$args): Response
{
// Получаем ID статьи из параметров маршрута
$blogId = $request->getAttribute('id');
if (!$blogId) {
return response()->error(400, 'ID статьи не указан');
}
// Проверяем существование и владельца
$blog = \Flute\Modules\Blog\Database\Entities\Article::findByPK($blogId);
if (!$blog) {
return response()->error(404, 'Статья не найдена');
}
if ($blog->author_id !== user()->id && !user()->can('admin.blog')) {
return response()->error(403, 'Доступ запрещён');
}
// Передаём управление дальше
return $next($request);
}
}Генерация URL
Основные методы
<?php
// Через хелпер (рекомендуется)
$url = route('news.show', ['id' => 123]);
// Через роутер
$url = router()->url('news.show', ['id' => 123]);Примеры
<?php
// Простой маршрут без параметров
$homeUrl = route('home');
// Результат: /
// С параметром в пути
$newsUrl = route('news.show', ['id' => 123]);
// Результат: /news/123
// С несколькими параметрами
$articleUrl = route('blog.article', ['category' => 'php', 'slug' => 'my-article']);
// Результат: /blog/php/my-article
// С query параметрами
$searchUrl = route('search') . '?' . http_build_query(['q' => 'flute', 'page' => 2]);
// Результат: /search?q=flute&page=2Проверка существования
<?php
// Проверка существования маршрута по пути
if (router()->hasRoute('/news', ['GET'])) {
echo "Маршрут существует";
}
// Проверка по имени
if (router()->getRoutes()->get('news.show')) {
$url = route('news.show', ['id' => 123]);
}Вспомогательные методы
<?php
// Получение текущего маршрута
$currentRoute = router()->getCurrentRoute();
// Получение всех маршрутов
$allRoutes = router()->getRoutes();События маршрутизации
Router отправляет события в процессе работы:
<?php
use Flute\Core\Router\Events\RoutingStartedEvent;
use Flute\Core\Router\Events\OnRouteFoundEvent;
use Flute\Core\Router\Events\RoutingFinishedEvent;
// При инициализации роутера
events()->addListener(RoutingStartedEvent::NAME, function($event) {
// Роутер начал обработку
});
// При нахождении маршрута
events()->addListener(OnRouteFoundEvent::NAME, function($event) {
$route = $event->getRoute();
$request = $event->getRequest();
// Можно модифицировать запрос или добавить логику
});
// После обработки запроса
events()->addListener(RoutingFinishedEvent::NAME, function($event) {
// Роутер завершил обработку
});Кеширование
Router автоматически кеширует скомпилированные маршруты:
storage/app/cache/routes_compiled_front.php - фронтенд маршруты
storage/app/cache/routes_compiled_admin.php - админ маршрутыВ debug режиме кеш автоматически инвалидируется. В production кеш сохраняется до изменения файлов маршрутов.
Лучшие практики
Именуйте маршруты
Всегда используйте понятные имена с точечной нотацией:
->name('news.show') // Хорошо
->name('show_news') // ПлохоГруппируйте по модулям
Начинайте имена с названия модуля для избежания конфликтов.
Используйте middleware
Всегда добавляйте проверку прав и CSRF защиту:
router()->post('/news', [NewsController::class, 'store'])
->middleware(['auth', 'csrf', 'can:create_news']);Структурируйте группы логически
router()->group(['prefix' => '/news'], function () {
// Публичные маршруты
router()->get('/', [NewsController::class, 'index'])->name('news.index');
router()->get('/{id}', [NewsController::class, 'show'])->name('news.show');
// Защищённые маршруты
router()->group(['middleware' => 'auth'], function () {
router()->post('/', [NewsController::class, 'store'])->name('news.store');
router()->put('/{id}', [NewsController::class, 'update'])->name('news.update');
});
});Минимизируйте middleware
Для часто используемых маршрутов избегайте избыточных middleware.