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.

  • requestScopeEnabled binds Request::class as scoped for each request lifecycle.

  • Response::view() uses the same intermix container 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): RouteInterface

  • post(string $path, array|string|callable $handler, string|array|null $nameOrOpts = null): RouteInterface

  • put(string $path, array|string|callable $handler, string|array|null $nameOrOpts = null): RouteInterface

  • patch(string $path, array|string|callable $handler, string|array|null $nameOrOpts = null): RouteInterface

  • delete(string $path, array|string|callable $handler, string|array|null $nameOrOpts = null): RouteInterface

  • options(string $path, array|string|callable $handler, string|array|null $nameOrOpts = null): RouteInterface

  • head(string $path, array|string|callable $handler, string|array|null $nameOrOpts = null): RouteInterface

  • group(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): string

  • Route::temporaryUrlFor(string $name, array $params = [], array $query = [], ?int $ttl = null, bool $absolute = false, ?string $payloadMode = null): string

  • Route::temporaryUrlUntil(string $name, DateTimeInterface|int $expiresAt, array $params = [], array $query = [], bool $absolute = false, ?string $payloadMode = null): string