proteins / route
A simple and fast URL router
Requires
- php: ^7.2
- proteins/event: ^1.0.0
- proteins/extensions: ^1.0.0
- proteins/filter: ^1.0.0
- proteins/options: ^1.0.0
- proteins/request: ^1.0.0
- proteins/response: ^1.0.0
- proteins/url: ^1.0.0
README
Protein | Route
A simple and fast URL router
Install
composer require proteins/route
Require the global class via :
use Proteins\Route;
URL mapping
You can define a route via the on
method.
Route::on('/hello',function(){ echo 'Hello, Friend!'; });
This is the simplest form to define an HTTP GET
route responding on URL /hello
.
You can map the same route to multiple request methods using the fluent api interface.
Route::on('/hello') ->via('get','post') ->with(function(){ echo 'Hello, Friend!'; });
The via
method accepts an array of string for handled HTTP request methods.
The with
method binds a callable function to the route.
If you need to map various HTTP methods to different callbacks for a single URL (like exposing a resource via a REST API) the map
method allows you to pass a method
=> callback
dictionary.
Route::map('/entity(/:id)/?',[ 'get' => function($id=null){ // READ: fetch the $id element or all if $id === null }, 'post' => function($id=null){ // CREATE: build a new element }, 'put' => function($id=null){ // UPDATE: modify $id element's properties }, 'delete' => function($id=null){ // DELETE: delete $id element }, ])
The any
shorthand will trigger the route responding on URL /hello
for any HTTP verb.
Route::any('/hello',function(){ echo 'Hello, World!'; });
This is the same as doing :
Route::on('/hello') ->via('*') ->with(function(){ echo 'Hello, World!'; });
URL pattern matching and parameters extraction
The route pattern is essentially a Regular Expression with some slight differencies.
The pattern is ALWAYS matched against the end of the REQUEST_URI
parameter (stripped of the query string).
Rules:
- every
(...)
group becomes optional - you can extract parameters via
:named_parameter
- the pattern can't contain the
#
character
Examples:
Route::on('/element(/:id)/?',function($id=null){ if (null === $id){ $result = get_all_elements(); } else { $result = get_element_by_id($id); } print_r($result); });
In this example the optional (is in a (...)
group) :id
is extracted when present, and the route can be optionally terminated by a /
.
This route handles all of these request:
/element
/element/
/element/123
/element/123/
/element/1919191
/element/462635
- etc..
But, as you can see, this example handles also /element/fooo
.
If we want to give format rules to an extracted parameters we can use the rules
method.
The rules
method accepts a named_parameter
=> regex
dictionary.
rules([ 'parameter_name' => 'parameter_regex_pattern' ])
We can strenghten the former example adding rules to the id
parameter for accepting only integer values (defined by the \d+ regex pattern).
Example:
Route::on('/element(/:id)/?',function($id=null){ if (null === $id){ $result = get_all_elements(); } else { $result = get_element_by_id($id); } print_r($result); }) ->rules([ 'id' => '\d+' ]);
Route groups
You can encapsulate routes based on a prefix pattern. If the current request doesn't match the group URL pattern, relative routes definition are not registered.
Note: This behaviour is controlled by the
core.route.pruning
flag.
This feature can be used for response-time optimization and for mounting route trees to a dynamic URL prefix.
You can define multiple nested route groups.
Examples:
Admin section
Route::group('/admin',function(){ Route::on('/',function(){ echo "Admin Index"; }); Route::on('/login') ->via('get','post') ->with(function(){ // Handle login }); Route::on('/logout',function(){ // handle logout }); Route::group('/dashboard',function(){ Route::on('/',function(){ // Dashboard }); Route::on('/details',function(){ // Dashboard Details }); }); });
Route groups with dynamic parameters
RouteGroups can have dynamic parameters that will be extracted like normal Routes.
Route::group("/book/:id", function($id){ $book = new Book($id); Route::on("/", function() use ($book){ return $book; }); Route::on("/:field", function($field) use ($book){ return $book->$field; }); });
Route middlewares
You can append a list of middlewares before
and after
a Route, or a RouteGroup.
If a middleware returns
false
the entire route execution halts.
Middlewares can be chained, the before
s will be executed in reverse declaration order (FIFO), the after
s in direct declaration order (LIFO).
Route::on('/', "[TEST]" ) ->before(function(){ echo "(B1)"; }) ->before(function(){ echo "(B2)"; }) ->after(function(){ echo "(A1)"; }) ->after(function(){ echo "(A2)"; });
Gives this output :
(B2)(B1)[TEST](A1)(A2)
You can apply a middleware to multiple routes a single time using a RouteGroup :
Route::group('/private',function(){ Route::on('/', ... ); Route::on('/dashboard', ... ); Route::on('/profile', ... ); Route::on('/settings', ... ); })->before(function(){ if ( ! user_authorized() ) { Response::error(403,"Forbidden"); return false; } });
Dispatching routes
Remember to invoke the dispatch
method before script end for route execution.
Route::on('/hello',function(){ echo 'Hello, Friend!'; }); // Run the route dispatcher. Route::dispatch();
You can override the request URI and method passing them as parameters to the dispatch method :
Route::dispatch($URL=null, $method=null, $return_route=false)
Example :
Route::dispatch('/my/forced/uri','OPTIONS');
The matched (if any) route can be returned without being automatically executed passing true to the return_route
parameter :
$matched_route = Route::dispatch(null,null,true);
Route can't find a match. (HTTP 404)
When no routes matches the current request, the 404
event is triggered.
You can append a view to the Response
to show a courtesy page.
Event::on(404,function(){ Response::html( View::from('errors/404') ); });
Render shortcuts
Instead of the rendering callback, you can also pass a string or a view, for direct rendering.
Closure
Route::on('/',function(){ return View::from('index'); });
<h1>I'm the index!</h1>
A View
Route::on('/', View::from('index') );
<h1>I'm the index!</h1>
A string (not a callable one)
Route::on('/', 'Not a callable string' );
Not a callable string
An object
Route::on('/',(object)[ 'alpha' => 123, 'beta' => [1,2,3] ]);
{"alpha":123,"beta":[1,2,3]}
HTTP/2 Resource Push
You can push resources directly from the Route and RouteGroup definitions.
The syntax is the same as for the Response::push
method.
Route::on('/', function() { return View::from('index'); })->push([ 'style' => '/assets/css/main.css', 'script' => [ '/assets/js/vendors.js', '/assets/js/main.js', ], ]);
URL tagging and Reverse Routing
You can add a name tag to a route via the tag
method :
Route::on('/user/:id', function ($id) { return "USER[$id]"; })->tag('user');
A named route is retrieved via the Route::tagged($name)
method :
$user_route = Route::tagged('user');
To get the URL for a route use the getURL($params = [])
method :
echo $user_route->getURL();
As a shorthand, you can obtain the URL for the named route via the Route::URL($name, $params = [])
helper :
echo Route::URL('user');
/user
You can also pass an array to map and assign the dynamic values of the route.
echo Route::URL('user',[ 'id' => 123, ]);
/user/123
Note: The returned value of the
Route::URL
method is an [[URL]] object.
Events and Filters
Route has the Events trait behaviour, the only difference is that the Events::on
method is renamed to onEvent
to avoid collisions with the Route::on
handler.
Route::onEvent('start',function($route, $args, $method){ echo "Called route.\n"; });