Router API Reference#
Complete reference for routing APIs in Webrick.
Table of Contents#
Route Registration#
Using Registrar#
use Infocyph\Webrick\Router\Definition\Registrar;
use Infocyph\Webrick\Router\Matching\ShardedMatcher;
use Infocyph\Webrick\Router\Kernel\RouterKernel;
use Psr\Log\NullLogger;
$register = function (Registrar $r): void {
$r->get('/users', [UserController::class, 'index'], 'users.index');
$r->post('/users', [UserController::class, 'store'], 'users.store');
};
$kernel = RouterKernel::bootWithRegistrar(
log: new NullLogger(),
matcher: ShardedMatcher::make(__DIR__ . '/.route-cache'),
register: $register,
routeCache: __DIR__ . '/.route-cache',
);
Kernel DI Integration#
RouterKernel::bootWithRegistrar() can be wired directly with InterMix features:
use Infocyph\InterMix\DI\Container;
use Infocyph\InterMix\DI\Invoker;
use App\Providers\AuthProvider;
use App\Providers\CacheProvider;
$container = Container::instance('intermix');
$invoker = Invoker::with($container);
$kernel = RouterKernel::bootWithRegistrar(
log: new NullLogger(),
matcher: ShardedMatcher::make(__DIR__ . '/.route-cache'),
register: $register,
invoker: $invoker, // or container: $container
serviceProviders: [
AuthProvider::class,
CacheProvider::class,
],
preGlobalTags: ['webrick.middleware.pre'],
postGlobalTags: ['webrick.middleware.post'],
requestScopeEnabled: true, // enterScope/leaveScope per handle()
);
Notes:
Tagged middleware are appended after explicit
preGlobal/postGlobal.requestScopeEnabledbindsRequest::classas scoped for each request lifecycle.Response::view()uses the sameintermixcontainer path as kernel DI by default.
Using Facade#
use Infocyph\Webrick\Router\Facade\Router as Route;
Route::get('/posts', [PostController::class, 'index']);
Route::post('/posts', [PostController::class, 'store']);
Route Facade#
Available Methods#
Route::get($path, $handler, $nameOrOpts = null);
Route::post($path, $handler, $nameOrOpts = null);
Route::put($path, $handler, $nameOrOpts = null);
Route::patch($path, $handler, $nameOrOpts = null);
Route::delete($path, $handler, $nameOrOpts = null);
Route::options($path, $handler, $nameOrOpts = null);
Route::head($path, $handler, $nameOrOpts = null);
Multi-Method Endpoints#
Route::get('/form', fn() => Response::create('<form>...</form>', 200, [
'Content-Type' => 'text/html; charset=UTF-8'
]));
Route::post('/form', fn(Request $r) => Response::json(['submitted' => true]));
HTTP Verb Methods#
GET#
Route::get('/users', function() {
return Response::json(UserRepository::all());
});
POST#
Route::post('/users', function(Request $r) {
$user = UserRepository::create($r->input());
return Response::json($user, 201)
->withHeader('Location', "/users/{$user['id']}");
});
PUT#
Route::put('/users/{id:int}', function(Request $r, int $id) {
$user = UserRepository::update($id, $r->input());
return Response::json($user);
});
PATCH#
Route::patch('/users/{id:int}', function(Request $r, int $id) {
$user = UserRepository::patch($id, $r->input());
return Response::json($user);
});
DELETE#
Route::delete('/users/{id:int}', function(int $id) {
UserRepository::delete($id);
return Response::noContent();
});
OPTIONS#
Route::options('/api/*', function() {
return Response::create('', 200, [
'Allow' => 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS'
]);
});
Route Groups#
Basic Group#
Route::group(callback: function() {
Route::get('/users', [UserController::class, 'index']);
Route::get('/users/{id:int}', [UserController::class, 'show']);
});
Prefix#
Route::group(prefix: '/api/v1', callback: function() {
// GET /api/v1/users
Route::get('/users', [UserController::class, 'index']);
// GET /api/v1/posts
Route::get('/posts', [PostController::class, 'index']);
});
Name Prefix#
Route::group(namePrefix: 'admin.', callback: function() {
// Name: admin.users.index
Route::get('/users', [AdminController::class, 'users'], 'users.index');
// Name: admin.settings
Route::get('/settings', [AdminController::class, 'settings'], 'settings');
});
Middleware#
Route::group(middleware: ['auth', 'throttle:60,60'], callback: function() {
Route::get('/profile', [ProfileController::class, 'show']);
Route::put('/profile', [ProfileController::class, 'update']);
});
Combined Options#
Route::group(
prefix: '/admin',
namePrefix: 'admin.',
middleware: ['auth', 'admin'],
callback: function() {
// GET /admin/users, name: admin.users, middleware: auth, admin
Route::get('/users', [AdminController::class, 'users'], 'users');
}
);
Nested Groups#
Route::group(prefix: '/api', callback: function() {
Route::group(prefix: '/v1', callback: function() {
// GET /api/v1/users
Route::get('/users', [UserController::class, 'index']);
});
Route::group(prefix: '/v2', callback: function() {
// GET /api/v2/users
Route::get('/users', [UserV2Controller::class, 'index']);
});
});
Route Parameters#
Required Parameters#
Route::get('/users/{id}', function(int $id) {
return Response::json(['id' => $id]);
});
Optional Parameters#
Route::get('/search/{query?}', function(?string $query = null) {
return Response::json(['query' => $query ?? 'all']);
});
Constrained Parameters#
// Integer constraint
Route::get('/users/{id:int}', function(int $id) { /* ... */ });
// Slug constraint
Route::get('/posts/{slug:slug}', function(string $slug) { /* ... */ });
// UUID constraint
Route::get('/resources/{uuid:uuid}', function(string $uuid) { /* ... */ });
// Hex constraint
Route::get('/colors/{hex:hex}', function(string $hex) { /* ... */ });
// Custom regex
Route::get('/codes/{code:[A-Z]{3}}', function(string $code) { /* ... */ });
Multiple Parameters#
Route::get('/posts/{year:int}/{month:int}/{slug:slug}',
function(int $year, int $month, string $slug) {
return Response::json([
'year' => $year,
'month' => $month,
'slug' => $slug
]);
}
);
Route Names#
Setting Names#
// Third parameter
Route::get('/users', [UserController::class, 'index'], 'users.index');
// Via options array
Route::get('/users', [UserController::class, 'index'], [
'name' => 'users.index'
]);
Checking Route Names#
Route::get('/current-route', function(Request $r) {
$name = $r->getAttribute('route.name');
return Response::json(['route' => $name]);
});
Middleware#
Per-Route Middleware#
Route::get('/protected', [SecretController::class, 'index'], [
'middleware' => ['auth', 'verified']
]);
// Or using facade shorthand
Route::get('/protected', [SecretController::class, 'index'])
->withMiddleware(['auth', 'verified']);
Middleware with Parameters#
Route::post('/api/data', [ApiController::class, 'store'], [
'middleware' => ['throttle:30,60', 'verifySignedUrl']
]);
Group Middleware#
Route::group(middleware: ['auth'], callback: function() {
Route::get('/profile', [ProfileController::class, 'show']);
Route::put('/profile', [ProfileController::class, 'update']);
});
Domain Routing#
Domain Constraint#
Route::group(domain: 'api.example.com', callback: function() {
Route::get('/users', [ApiController::class, 'users']);
});
Subdomain Wildcards#
Route::group(domain: '{account}.example.com', callback: function() {
Route::get('/', function(string $account) {
return Response::json(['account' => $account]);
});
});
Multiple Domains#
Route::group(domain: 'admin.example.com', callback: function() {
Route::get('/dashboard', [AdminController::class, 'dashboard']);
});
Route::group(domain: 'api.example.com', callback: function() {
Route::get('/users', [ApiController::class, 'users']);
});
URL Generation#
From Named Routes#
// In handler
$url = Route::urlFor('users.show', ['id' => 42]);
// '/users/42'
// Absolute URL
$url = Route::urlFor('users.show', ['id' => 42], absolute: true);
// 'https://example.com/users/42'
With Query Parameters#
$url = Route::urlFor('users.index', query: ['page' => 2, 'sort' => 'name']);
// '/users?page=2&sort=name'
Signed URLs#
$signed = Route::signedUrlFor('download', ['file' => 'report.pdf']);
$temp = Route::temporaryUrlFor('download', ['file' => 'report.pdf'], ttl: 3600);
Route Caching#
Build Cache#
use Infocyph\Webrick\Support\RouteCache;
RouteCache::build([
'cache' => __DIR__ . '/.route-cache',
'register' => function($r) {
require __DIR__ . '/routes/web.php';
require __DIR__ . '/routes/api.php';
}
]);
Use Cache#
$kernel = RouterKernel::bootWithRegistrar(
log: new \Psr\Log\NullLogger(),
matcher: \Infocyph\Webrick\Router\Matching\ShardedMatcher::make(__DIR__ . '/.route-cache'),
register: static function (\Infocyph\Webrick\Router\Definition\Registrar $registrar): void {
unset($registrar);
require __DIR__ . '/routes.php';
},
routeCache: __DIR__ . '/.route-cache',
);
Clear Cache#
RouteCache::clear([
'matcher' => 'sharded',
'cache' => __DIR__ . '/.route-cache',
]);
Common Patterns#
RESTful Resource#
Route::get('/posts', [PostController::class, 'index'], 'posts.index');
Route::get('/posts/create', [PostController::class, 'create'], 'posts.create');
Route::post('/posts', [PostController::class, 'store'], 'posts.store');
Route::get('/posts/{id:int}', [PostController::class, 'show'], 'posts.show');
Route::get('/posts/{id:int}/edit', [PostController::class, 'edit'], 'posts.edit');
Route::put('/posts/{id:int}', [PostController::class, 'update'], 'posts.update');
Route::delete('/posts/{id:int}', [PostController::class, 'destroy'], 'posts.destroy');
API Versioning#
Route::group(prefix: '/api', callback: function() {
Route::group(prefix: '/v1', namePrefix: 'v1.', callback: function() {
Route::get('/users', [V1\UserController::class, 'index'], 'users');
});
Route::group(prefix: '/v2', namePrefix: 'v2.', callback: function() {
Route::get('/users', [V2\UserController::class, 'index'], 'users');
});
});
Fallback Route#
// Must be last route registered
Route::get('/{path:.*}', function(string $path) {
return Response::json([
'error' => 'Not Found',
'path' => $path
], 404);
});
Method Summary#
Registrar#
get(string $path, array|string|callable $handler, string|array|null $nameOrOpts = null): RouteInterfacepost(string $path, array|string|callable $handler, string|array|null $nameOrOpts = null): RouteInterfaceput(string $path, array|string|callable $handler, string|array|null $nameOrOpts = null): RouteInterfacepatch(string $path, array|string|callable $handler, string|array|null $nameOrOpts = null): RouteInterfacedelete(string $path, array|string|callable $handler, string|array|null $nameOrOpts = null): RouteInterfaceoptions(string $path, array|string|callable $handler, string|array|null $nameOrOpts = null): RouteInterfacehead(string $path, array|string|callable $handler, string|array|null $nameOrOpts = null): RouteInterfacegroup(array|string|null $prefix = null, string|array|Closure|null $domain = null, array|Closure $middleware = [], string|Closure|null $namePrefix = null, ?Closure $callback = null): void
Route Facade#
All Registrar methods
Chainable middleware:
->withMiddleware(array $middleware)Chainable name:
->withName(string $name)
URL Generation#
Route::urlFor(string $name, array $params = [], array $query = [], bool $absolute = false): string
URL Signing#
Route::signedUrlFor(string $name, array $params = [], array $query = [], ?int $ttl = null, bool $absolute = false, ?string $payloadMode = null): stringRoute::temporaryUrlFor(string $name, array $params = [], array $query = [], ?int $ttl = null, bool $absolute = false, ?string $payloadMode = null): stringRoute::temporaryUrlUntil(string $name, DateTimeInterface|int $expiresAt, array $params = [], array $query = [], bool $absolute = false, ?string $payloadMode = null): string