Routing
Concept Core uses League Route
on top of PSR-7. Routes are defined in PHP files; a single shared instance of
Concept\Core\Http\Routing\Router (implements RouterInterface) serves the entire application.
Context: request lifecycle, config/routes.php.
Loading route files
In config/routes.php you define the file list (routes.list) and optionally
route interceptors (routes.interceptors). Loaded by
HttpServiceProvider when RouterInterface is first created:
return [
'routes' => [
'interceptors' => [
// AclRouteAuthorization::class, // RouteInterceptorInterface
],
'list' => [
dirname(__DIR__) . '/routes/web.php',
],
],
];
Additional files from components are loaded during
ComponentsServiceProvider::boot() — a component returns the path to
routes.php via ComponentInterface::routes()
See structure with components.
Each route file has access to the $router variable
(type League\Route\Router / RouterInterface):
<?php declare(strict_types=1);
use League\Route\Router;
/** @var Router $router */
$router->get('/path', $handler);
Global route interceptors (ACL, policies) are registered in
routes.interceptors — see the dedicated section
Route interceptors.
Handlers
League Route supports several callable forms:
Controller [Class, method]
The recommended approach — a controller method resolved from the DI container:
use Concept\App\Controllers\IndexController;
$router->get('/', [IndexController::class, 'index'])->setName('home');
The controller instance and method arguments are injected by RouteStrategy
(FormRequest, ServerRequestInterface, URL parameters, Caster).
More details — Controllers.
Closure
use Concept\Core\Http\Contracts\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
$router->get('/health', function (ResponseFactoryInterface $response): ResponseInterface {
return $response->json(['status' => 'ok']);
});
For simple endpoints; for complex logic prefer a controller.
HTTP methods
| Router method | HTTP |
|---|---|
get() | GET |
post() | POST |
put() | PUT |
patch() | PATCH |
delete() | DELETE |
map() | multiple methods |
URL parameters
Segments in curly braces end up in request attributes and handler arguments:
$router->get('/admin/users/edit/{id:number}', [UserController::class, 'edit']);
The :number suffix is a League Route constraint (digits only).
Others: :alpha, :alphanum, :segment, etc.
Without a suffix — any segment ({token}).
In the controller, a parameter with the same name and type (via CasterInterface):
public function edit(int $id): ResponseInterface
Route groups
Shared URI prefix and middleware for a group:
use League\Route\RouteGroup;
use Concept\Components\AuthAdmin\Middlewares\AuthMiddleware;
$router->group('/admin', function (RouteGroup $route) {
$route->get('/', [AdminController::class, 'index'])->setName('admin.home');
$route->get('/logout', [AdminController::class, 'logout'])->setName('admin.logout');
})->lazyMiddleware(AuthMiddleware::class);
URLs /admin and /admin/logout pass through AuthMiddleware.
Nested groups are supported.
Middleware on routes
Global — for all routes in the file (usually at the top of routes/web.php).
The minimal skeleton does not attach middleware
$router->lazyMiddleware(VerifyCsrfTokenMiddleware::class);
$router->lazyMiddleware(ShareViewDataMiddleware::class);
On a group or route — the chain is extended for matches in that scope:
$router->post('/projects/store', [ProjectController::class, 'store'])
->setName('projects.store')
->lazyMiddleware(ActiveUserMiddleware::class);
lazyMiddleware — the class is resolved from the container at request time.
More details — Middleware.
Named routes
Each route can be given a name for URL generation:
$router->get('/login', [CabinetController::class, 'showLogin'])->setName('cabinet.login');
In PHP via UrlGeneratorInterface:
// path from root: /admin/users/edit/5
$path = $urlGenerator->uri('admin.user.edit', ['id' => 5]);
// full URL: https://example.com/admin/users/edit/5
$fullUrl = $urlGenerator->url('admin.user.edit', ['id' => 5]);
$origin = $urlGenerator->base();
Redirect by name — ResponseFactoryInterface::redirectByName():
return $this->response->redirectByName('admin.users');
In Twig — AppExtension in skeleton:
{{ uri('cabinet.login') }}
{{ url('admin.user.edit', { id: user.id }) }}
{{ base_url() }}
Component routes
A component returns the absolute path to routes.php via
ComponentInterface::routes().
Example: src/Components/AuthAdmin/routes.php with prefixes /admin,
/admin/users.
All routes end up in one Router — avoid duplicate names
(setName) across files.
Listing routes (CLI)
php bin/console.php route:list
php bin/console.php route:list --full-middleware
php bin/console.php route:list -F
The route:list command prints method, URI, name, handler, and middleware.
Requires ConsoleServiceProvider and registration of RouteListCommand in
config/commands.php (already present in skeleton).
Tips
- Keep public routes in
routes/web.php, modular ones inComponent/routes.php. - Always name routes that appear in templates or redirects.
- POST/PUT/PATCH/DELETE with forms — together with
VerifyCsrfTokenMiddlewareand a CSRF field. - Protected areas —
group()+ auth middleware; ACL — route interceptors.