Fracture\Routing
, (*1)
Introduction
This component is a simple routing library, that is made to be easily compatible with other libs. It does not include any functionality for dispatching. Instead it focuses on "packaging" the user's input in an abstracted representation of request., (*2)
Installation
You can add the library to your project using composer with following command:, (*3)
composer require fracture/routing
It will also install fracture/http
as a dependency., (*4)
Usage
The following code illustates the process of initializing the abstraction of an HTTP request and routing said request., (*5)
<?php
require '/path/to/vendor/autoload.php';
/*
* Setting up request abstraction
*/
$builder = new Fracture\Http\RequestBuilder;
$request = $builder->create([
'get' => $_GET,
'files' => $_FILES,
'server' => $_SERVER,
'post' => $_POST,
'cookies'=> $_COOKIE,
]);
$uri = isset($_SERVER['REQUEST_URI'])
? $_SERVER['REQUEST_URI']
: '/';
$request->setUri($uri);
/*
* Defining the config
*/
$configuration = [
'primary' => [
'notation' => '[:id]/:resource',
'conditions' => [
'id' => '[0-9]+',
],
],
'fallback' => [
'notation' => ':any',
'conditions' => [
'any' => '.*',
],
'defaults' => [
'resource' => 'landing',
],
],
];
/*
* Routing the request
*/
$router = new Fracture\Routing\Router(new Fracture\Routing\RouteBuilder);
$router->import($configuration);
$router->route($request);
// The $request now is fully initialized.
var_dump($request->getParameter('resource'));
Definition of routes
The import()
method of the router expects a list of defined routes. Each route is an array containing 'notation'
element. It also can optionally have 'conditions'
and 'defaults'
fields., (*6)
'primary' => [
'notation' => ':resource[/:action]',
'conditions' => [
// list custom conditions
],
'defaults' => [
// list of fallback values
]
],
When routers is attempting to match the URI to the list of defined routes, it iterates starting from top element and continues till either a match is found or the list has ended. Therefore to increase the priority of any of defined routes, you move it higher in the list., (*7)
The notation is a human-readable format for defining the structure of URI, which you are attempting to match. It can contain 3 identifiable sets of parts, all of which can be seen in this example:, (*8)
document[/view]/:name[.:extension]
These parts are:, (*9)
-
tokens, (*10)
Each token has starts with colon and name written using only letters from English alphabet (or :[a-zA-Z]+
as a regular expression). This shape of the token is defined in Pattern::REGKEY
., (*11)
In the above given example the defined tokens were :name
and :extension
., (*12)
-
static text, (*13)
These are parts of notation, which has not direct computational value, but only serve to structure the URI and make it easier to read and/or identify., (*14)
In the above given example the static text is document
, /view
, /
and .
(dot)., (*15)
-
optional element, (*16)
If any part of notation is wrapped in []
(square brackets), it becomes non-mandatory. Any notation's part and combination of parts can be defined as optional element. This also means that optional elements can be nested., (*17)
In the example above the part, that are defined as optional were /view
and .:extension
., (*18)
Conditions
For each route it is possible to define custom conditions, that the tokens will be expected to match. By default every token is attempting to match an URI fragment, that does not contain /\.,;?
. To change this behavior each route definition can optionally have a conditions
element., (*19)
The conditions are set as array of key => value
pairs, where keys correspond to names of tokens and values contain regular expression fragments. This is demonstrated in the following excerpt from route configuration array:, (*20)
'notation' => ':project/[:name]/:iteration',
'conditions' => [
'name' => '[A-Z][a-z]+-[0-9]{2}',
'iteration' => '[0-9]+',
],
In this example you see a notation with three defined tokens, where :name
token is optional. There also are two custom conditions defines, assigned to tokens :name
and :iteration
., (*21)
Defaults
When URI pattern has optional parts, you inevitably will have some requests where those parts were missing. In which case by default the Fracture\Request
will return null
, when trying to retrieve that parameter. But this behavior is not always the most useful., (*22)
If you want for optional URI part to have defined fallback values, which are used, when fragment was absent. That can be done by appending the definition of a route:, (*23)
'notation' => ':project/[:name]',
'defaults' => [
'name' => 'unnamed',
],
In the example above, if notation is matched, but the corresponding was not present in URI, the request abstraction will receive "unnamed"
as value for 'name'
parameter., (*24)
This feature can also be used to add "silent parameters" for a matched URI:, (*25)
'notation' => 'verify/[:hash]',
'conditions' => [
'hash' => '[a-z0-9]{32}',
],
'defaults' => [
'resource' => 'registration',
'action' => 'complete',
],
By having these "silent parameters", your code is not restricted to only using string-values that were found in URI., (*26)
Use of routed request
See documentation for fracture/http., (*27)
Various tips
Cleaner configuration
In a real-world project your application will almost always have more than couple routes. Which can result in extensive configuration, that would make the initialization phase of your project (like a bootstrap file) hard to read and filled with clutter., (*28)
To prevent that, you can segregate the configuration into a dedicated file., (*29)
<?php
// other code
$configuration = json_decode(file_get_contents(__DIR__ . '/config/routes.json'), true);
$router = new Fracture\Routing\Router(new Fracture\Routing\RouteBuilder);
$router->import($configuration);
This can also be combined with environment variables, for differentiating between development, staging and production environments., (*30)