Router
The Router handles HTTP route registration, request dispatching, middleware execution, and URL generation.
Namespace: VelvetCMS\Http\Routing\Router
Definition #
class Router
{
public function __construct(EventDispatcher $events);
// Route registration
public function get(string $path, callable|array $handler, ?string $name = null): RouteDefinition;
public function post(string $path, callable|array $handler, ?string $name = null): RouteDefinition;
public function put(string $path, callable|array $handler, ?string $name = null): RouteDefinition;
public function delete(string $path, callable|array $handler, ?string $name = null): RouteDefinition;
public function patch(string $path, callable|array $handler, ?string $name = null): RouteDefinition;
public function any(string $path, callable|array $handler, ?string $name = null): RouteDefinition;
// Route groups
public function group(array $attributes, callable $callback): void;
// Middleware
public function middleware(string|array|callable $middleware): self;
public function registerMiddleware(string $name, callable|string $middleware): void;
public function pushMiddleware(callable|string $middleware): void;
// URL generation
public function url(string $name, array $params = []): string;
// Dispatching
public function dispatch(Request $request): Response;
// Caching
public function getRouteDefinitions(): array;
public function loadCachedRoutes(array $cachedRoutes): void;
public function hasCachedRoutes(): bool;
}
Route Registration #
HTTP Method Shortcuts #
Register routes for specific HTTP methods:
$router->get('/posts', [PostController::class, 'index']);
$router->post('/posts', [PostController::class, 'store']);
$router->put('/posts/{id}', [PostController::class, 'update']);
$router->delete('/posts/{id}', [PostController::class, 'delete']);
$router->patch('/posts/{id}', [PostController::class, 'patch']);
any() #
Register a route that responds to any HTTP method:
$router->any('/webhook', [WebhookController::class, 'handle']);
Route Handlers #
Routes accept either a controller/method array or a closure:
// Controller action
$router->get('/posts', [PostController::class, 'index']);
// Closure
$router->get('/health', fn(Request $request) => Response::json(['status' => 'ok']));
// Closure with route parameters
$router->get('/posts/{id}', function(Request $request, string $id) {
return Response::json(['id' => $id]);
});
Route Parameters #
Required Parameters #
Use {name} for required segments:
$router->get('/posts/{id}', [PostController::class, 'show']);
$router->get('/users/{userId}/posts/{postId}', [PostController::class, 'showUserPost']);
Optional Parameters #
Use {name?} for optional segments:
$router->get('/posts/{category?}', [PostController::class, 'index']);
Wildcard Parameters #
Use {name*} for greedy matching (captures nested paths):
// Matches /docs/getting-started, /docs/api/router, etc.
$router->get('/docs/{path*}', [DocsController::class, 'show']);
Accessing Parameters #
Route parameters are passed to handlers after the Request:
// Controller method
public function show(Request $request, string $id): Response
{
// $id contains the route parameter value
}
// Closure
$router->get('/posts/{id}', function(Request $request, string $id) {
return Response::json(['id' => $id]);
});
Named Routes #
Name routes for URL generation:
$router->get('/posts', [PostController::class, 'index'], 'posts.index');
$router->get('/posts/{id}', [PostController::class, 'show'], 'posts.show');
$router->get('/posts/{id}/edit', [PostController::class, 'edit'], 'posts.edit');
Generate URLs using route names:
$url = $router->url('posts.index'); // /posts
$url = $router->url('posts.show', ['id' => 5]); // /posts/5
// Using helper function (respects tenant prefix)
$url = route('posts.show', ['id' => 5]);
Middleware #
Route Middleware #
Attach middleware to specific routes:
$router->get('/admin', [AdminController::class, 'index'])
->middleware('auth');
$router->get('/api/data', [ApiController::class, 'data'])
->middleware(['auth', 'throttle']);
Registering Middleware Aliases #
Register middleware classes with aliases:
$router->registerMiddleware('auth', AuthMiddleware::class);
$router->registerMiddleware('throttle', ThrottleMiddleware::class);
$router->registerMiddleware('cors', CorsMiddleware::class);
Global Middleware #
Add middleware that runs on all routes:
$router->pushMiddleware(ErrorHandlerMiddleware::class);
$router->pushMiddleware('cors');
Writing Middleware #
Middleware must implement MiddlewareInterface (from VelvetCMS\Contracts) or be a callable:
use VelvetCMS\Contracts\MiddlewareInterface;
use VelvetCMS\Http\Request;
use VelvetCMS\Http\Response;
class AuthMiddleware implements MiddlewareInterface
{
public function handle(Request $request, callable $next): Response
{
if (!session('user_id')) {
return Response::redirect('/login');
}
return $next($request);
}
}
Route Groups #
group() #
Group routes that share a common prefix and/or middleware:
public function group(array $attributes, callable $callback): void
Parameters:
$attributes— Associative array with optionalprefix(string) andmiddleware(string or array)$callback— Receives the Router instance; define routes inside
Behavior:
- Prefixes are concatenated when groups are nested
- Middleware is merged — outer group middleware runs first
- Groups are scoped — routes outside the group are unaffected
- Per-route
->middleware()stacks on top of group middleware
$router->group(['prefix' => '/api/v1', 'middleware' => ['auth', 'throttle']], function (Router $r) {
$r->get('/users', [UserController::class, 'index']); // /api/v1/users
$r->post('/users', [UserController::class, 'store']); // /api/v1/users
$r->get('/users/{id}', [UserController::class, 'show']); // /api/v1/users/{id}
});
Request Dispatching #
dispatch() #
Match and execute a route for the given request:
public function dispatch(Request $request): Response
The router:
- Matches the request path and method to a route
- Supports method spoofing via
_methodPOST field - Executes global middleware, then route middleware
- Calls the route handler
- Returns a Response (auto-converts strings and arrays)
Return Value Handling #
Handlers can return various types:
// String → HTML response
$router->get('/hello', fn() => '<h1>Hello</h1>');
// Array → JSON response
$router->get('/api/data', fn() => ['status' => 'ok']);
// Response object (preferred)
$router->get('/posts', fn() => Response::json($posts));
Method Spoofing #
HTML forms only support GET and POST. Use _method to spoof other methods:
<form method="POST" action="/posts/5">
<input type="hidden" name="_method" value="DELETE">
<button type="submit">Delete</button>
</form>
The router detects _method and routes to the appropriate handler.
Route Caching #
For production, cache routes to improve performance:
getRouteDefinitions() #
Export route definitions for caching:
$definitions = $router->getRouteDefinitions();
file_put_contents(
storage_path('cache/routes.php'),
'<?php return ' . var_export($definitions, true) . ';'
);
loadCachedRoutes() #
Load cached route definitions:
if ($router->hasCachedRoutes()) {
$cached = require storage_path('cache/routes.php');
$router->loadCachedRoutes($cached);
}
Events #
The router dispatches events during dispatch:
| Event | Payload | Description |
|---|---|---|
router.matching |
['method', 'path', 'original_method'] |
Before route matching |
router.matched |
['route', 'params'] |
After route matched |
Usage Examples #
Basic Application Routes #
// bootstrap/app.php or routes file
$router = app('router');
// Home page
$router->get('/', [HomeController::class, 'index'], 'home');
// Static pages
$router->get('/about', [PageController::class, 'about'], 'about');
$router->get('/contact', [PageController::class, 'contact'], 'contact');
$router->post('/contact', [PageController::class, 'submitContact'], 'contact.submit');
// Blog
$router->get('/blog', [BlogController::class, 'index'], 'blog.index');
$router->get('/blog/{slug}', [BlogController::class, 'show'], 'blog.show');
RESTful API Routes #
// API routes with authentication using groups
$router->group(['prefix' => '/api', 'middleware' => 'auth:api'], function (Router $r) {
$r->get('/posts', [ApiPostController::class, 'index'], 'api.posts.index');
$r->post('/posts', [ApiPostController::class, 'store'], 'api.posts.store');
$r->get('/posts/{id}', [ApiPostController::class, 'show'], 'api.posts.show');
$r->put('/posts/{id}', [ApiPostController::class, 'update'], 'api.posts.update');
$r->delete('/posts/{id}', [ApiPostController::class, 'destroy'], 'api.posts.destroy');
});
Route Groups #
// Group with prefix and middleware
$router->group(['prefix' => '/api', 'middleware' => ['auth', 'throttle']], function (Router $r) {
$r->get('/posts', [ApiPostController::class, 'index'], 'api.posts.index');
$r->post('/posts', [ApiPostController::class, 'store'], 'api.posts.store');
$r->delete('/posts/{id}', [ApiPostController::class, 'destroy'], 'api.posts.destroy');
});
// Nested groups — prefixes stack, middleware merges
$router->group(['prefix' => '/admin', 'middleware' => 'auth'], function (Router $r) {
$r->get('/dashboard', [AdminController::class, 'dashboard'], 'admin.dashboard');
$r->group(['prefix' => '/users'], function (Router $r) {
$r->get('/', [AdminUserController::class, 'index'], 'admin.users.index');
$r->get('/{id}', [AdminUserController::class, 'show'], 'admin.users.show');
});
});