slightly-interactive / router
A PSR-7 and PSR-15 compliant router.
Requires
- php: >=7.1
- phpunit/phpunit: ^9.2
- psr/container: ^1.0
- psr/http-message: ^1.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
README
Overview
Router is a HTTP request router, using PSR-7 requests and responses, and delegating to PSR-15 middlewares and request handlers. It requires a PSR-11 DI container.
Usage
Instantiate the router:
$router = new Router();
Give it a PSR-11 DI container:
$router->setDI($myContainer);
Configure routes:
$router->map('get', '/home', HomeController::class);
You can set individual middleware processors for an individual route:
$router->map('get', '/home', HomeController::class)
->middleware(MyMiddleware::class);
You can also create groups of middleware and add those to routes:
$router->middleware('common')
->add(CommonMiddleware1::class)
->add(CommonMiddleware2::class);
$router->map('get', '/home', HomeController::class)
->middlewareGroup('common')
->middleware(MyMiddleware::class);
The group doesn't have to exist, letting you toggle a set of middleware for a set of routes:
if ($condition) {
$router->middleware('option1')
->add(OptionalMiddleware1::class)
->add(OptionalMiddleware2::class);
}
$router->map('get', '/route1', Route1Controller::class)
->middlewareGroup('option1');
$router->map('get', '/route2', Route2Controller::class)
->middlewareGroup('option2');
The implementation accepts middleware
as a shorthand for
middlewareGroup
, if and only if the group already exists,
and the DI container has no named instance with that name.
Otherwise, the router can't tell the difference between a
named instance in the DI container and a group name.
This shorthand will be removed in a future version.
You can also set global groups of middleware. These will be run for all requests:
$router->middleware()
->add(GlobalMiddleware1::class)
->add(GlobalMiddleware2::class);
Route Matching Rules
Exact Match
The second parameter of Router::map is a string which is matched against the path part of the request URI.
At its simplest, it can be an exact string match:
/an/example/exact/path/match
This will match the path, regardless of the host, scheme, query string or fragment.
Parameter Match
When matching parameters, we consider the path split into parts
by slashes. Any whole part which starts with {
and ends with }
can match any value for the corresponding part of the URI path.
For example:
/entries/{year}/{month}/{day}
will match /entries/2021/08/14
and
modify the request to contain attributes:
'year' => '2021'
'month' => '08'
'day' => '14'
It's important to note that only full path parts can be matched, not partial ones. None of the following are valid:
/blah{suffix}
/{prefix}blah
/bl{infix}ah
/{slash/inside}
Multi Parameter Match
Using *
as a match part allows for matching of a variable number
of parts of the request URI path.
/blah/*
will match /blah/a/b/c
and create the following attribute:
'*0' => [ 'a', 'b', 'c' ]
More than one wildcard can be used. They will produce attributes with an incrementing number in their name:
/blah/*/x/*
will match /blah/a/b/x/c/d
and create the following
attributes:
'*0' => [ 'a', 'b' ]
'*1' => [ 'c', 'd' ]
Single parameters can be mixed with multi parameters. Single can
directly precede multi but not directly follow one, e.g.
/blah/{param1}/*/x/{param2}
will match /blah/a/b/c/x/y
and create the
attributes:
'param1' => 'a'
'*0' => [ 'b', 'c' ]
'param2' => 'y'
But /blah/*/{param}
will never match anything, as the final part
of any URI path will always match the *
and never {param}
.