Router API
The Flute CMS Router API is built on top of Symfony Routing with additional capabilities. It supports both attribute routing and programmatic route creation.
Getting the Router
<?php
use Flute\Core\Router\Router;
// Via DI container
$router = app(Router::class);
// Via helper
$router = router();Creating Routes
Basic HTTP Methods
<?php
// GET request
router()->get('/users', [UserController::class, 'index']);
// POST request
router()->post('/users', [UserController::class, 'store']);
// PUT request
router()->put('/users/{id}', [UserController::class, 'update']);
// DELETE request
router()->delete('/users/{id}', [UserController::class, 'destroy']);Additional Methods
<?php
// Multiple HTTP methods
router()->match(['GET', 'POST'], '/contact', [ContactController::class, 'handle']);
// Any HTTP method
router()->any('/webhook', [WebhookController::class, 'handle']);
// Static pages (returns view)
router()->view('/about', 'pages.about', ['title' => 'About Us']);
// Redirect
router()->redirect('/old-url', '/new-url', 301);Named Routes
<?php
router()->get('/news', [NewsController::class, 'index'])
->name('news.index');
router()->get('/news/{id}', [NewsController::class, 'show'])
->name('news.show');
// Using name to generate URL
$url = route('news.show', ['id' => 123]);
// Result: /news/123Middleware
<?php
// Single middleware
router()->post('/news', [NewsController::class, 'store'])
->middleware('auth')
->name('news.store');
// Multiple middleware
router()->put('/news/{id}', [NewsController::class, 'update'])
->middleware(['auth', 'csrf', 'can:edit_news'])
->name('news.update');Route Groups
<?php
// Group with common prefix and 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']);
});
// Nested groups
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']);
});
});Attribute Routing
Automatic Loading
Routes from attributes are loaded automatically when calling bootstrapModule() or loadRouterAttributes() in the provider:
<?php
// In provider
public function boot(\DI\Container $container): void
{
$this->bootstrapModule(); // Includes loadRouterAttributes()
}Manual Registration
<?php
// Register from directory
$router->registerAttributeRoutes(
[app_path('Modules/News/Controllers')],
'Flute\\Modules\\News\\Controllers'
);
// Register from specific class
$router->registerAttributeRoutesFromClass(NewsController::class);Built-in Middleware
Aliases
| Alias | Class | Description |
|---|---|---|
auth | IsAuthenticatedMiddleware | Authentication check |
guest | IsGuestMiddleware | Only for guests |
can | CanMiddleware | Permission check |
csrf | CsrfMiddleware | CSRF protection |
htmx | HtmxMiddleware | HTMX requests |
throttle | RateLimiterMiddleware | Rate limiting |
token | TokenMiddleware | API tokens |
ban.check | BanCheckMiddleware | Ban check |
maintenance | MaintenanceMiddleware | Maintenance mode |
Groups
<?php
// Built-in middleware groups
'web' => ['csrf', 'throttle'],
'api' => ['throttle', 'ban.check'],
'default' => ['ban.check', 'throttle', 'maintenance'],The default group is added to all routes automatically.
Middleware with Parameters
<?php
// Check permission
router()->get('/admin/users', [AdminController::class, 'users'])
->middleware('can:admin.users');
// Rate limiting: 100 requests per minute
router()->get('/api/data', [ApiController::class, 'data'])
->middleware('throttle:100,1');Registering Custom Middleware
Registering Alias
<?php
// Registering custom middleware
router()->aliasMiddleware('blog.owner', BlogOwnerMiddleware::class);
// Usage
router()->put('/blog/{id}', [BlogController::class, 'update'])
->middleware('blog.owner');Registering Group
<?php
router()->middlewareGroup('blog', [
'auth',
'blog.owner',
'throttle:10,1'
]);
// Using group
router()->group(['middleware' => 'blog'], function () {
router()->put('/blog/{id}', [BlogController::class, 'update']);
router()->delete('/blog/{id}', [BlogController::class, 'destroy']);
});Creating 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
{
/**
* Handle request
*
* @param FluteRequest $request Current request
* @param Closure $next Next handler
* @param mixed ...$args Additional arguments
*/
public function handle(FluteRequest $request, Closure $next, ...$args): Response
{
// Get article ID from route parameters
$blogId = $request->getAttribute('id');
if (!$blogId) {
return response()->error(400, 'Article ID not specified');
}
// Check existence and owner
$blog = \Flute\Modules\Blog\Database\Entities\Article::findByPK($blogId);
if (!$blog) {
return response()->error(404, 'Article not found');
}
if ($blog->author_id !== user()->id && !user()->can('admin.blog')) {
return response()->error(403, 'Access denied');
}
// Pass control further
return $next($request);
}
}URL Generation
Main Methods
<?php
// Via helper (recommended)
$url = route('news.show', ['id' => 123]);
// Via router
$url = router()->url('news.show', ['id' => 123]);Examples
<?php
// Simple route without parameters
$homeUrl = route('home');
// Result: /
// With parameter in path
$newsUrl = route('news.show', ['id' => 123]);
// Result: /news/123
// With multiple parameters
$articleUrl = route('blog.article', ['category' => 'php', 'slug' => 'my-article']);
// Result: /blog/php/my-article
// With query parameters
$searchUrl = route('search') . '?' . http_build_query(['q' => 'flute', 'page' => 2]);
// Result: /search?q=flute&page=2Check Existence
<?php
// Check existence of route by path
if (router()->hasRoute('/news', ['GET'])) {
echo "Route exists";
}
// Check by name
if (router()->getRoutes()->get('news.show')) {
$url = route('news.show', ['id' => 123]);
}Helper Methods
<?php
// Get current route
$currentRoute = router()->getCurrentRoute();
// Get all routes
$allRoutes = router()->getRoutes();Routing Events
Router dispatches events during processing:
<?php
use Flute\Core\Router\Events\RoutingStartedEvent;
use Flute\Core\Router\Events\OnRouteFoundEvent;
use Flute\Core\Router\Events\RoutingFinishedEvent;
// On router initialization
events()->addListener(RoutingStartedEvent::NAME, function($event) {
// Router started processing
});
// On finding route
events()->addListener(OnRouteFoundEvent::NAME, function($event) {
$route = $event->getRoute();
$request = $event->getRequest();
// Can modify request or add logic
});
// After processing request
events()->addListener(RoutingFinishedEvent::NAME, function($event) {
// Router finished processing
});Caching
Router automatically caches compiled routes:
storage/app/cache/routes_compiled_front.php - frontend routes
storage/app/cache/routes_compiled_admin.php - admin routesIn debug mode, cache is automatically invalidated. In production, cache persists until route files change.
Best Practices
Name Routes
Always use clear names with dot notation:
->name('news.show') // Good
->name('show_news') // BadGroup by Module
Start names with module name to avoid conflicts.
Use Middleware
Always add permission checks and CSRF protection:
router()->post('/news', [NewsController::class, 'store'])
->middleware(['auth', 'csrf', 'can:create_news']);Structure Groups Logically
router()->group(['prefix' => '/news'], function () {
// Public routes
router()->get('/', [NewsController::class, 'index'])->name('news.index');
router()->get('/{id}', [NewsController::class, 'show'])->name('news.show');
// Protected routes
router()->group(['middleware' => 'auth'], function () {
router()->post('/', [NewsController::class, 'store'])->name('news.store');
router()->put('/{id}', [NewsController::class, 'update'])->name('news.update');
});
});Minimize Middleware
For frequently used routes, avoid redundant middleware.