Key Takeaways
- Aura.Router is a web routing library for PHP 5.4, offering a simple and easy way to create SEO-friendly, REST-style URLs.
- The library contains four main files: Map.php, Route.php, RouteFactory.php and Exception.php. The RouteFactory is a factory class for creating new Route objects, while Map is a collection point for URI routes.
- You can add individual routes to a Map object using the add() method, or attach a series of routes using the attach() method. Routes are only created when the generate() and match() methods are called.
- Once routes are added, you can use the match() method of the Map object to recognize which route has been requested by a user. If a match is found, the method returns an instance of a Route object, otherwise it returns false.
- Aura.Router also allows for the generation of route paths in your view using the map’s generate() method. This feature is useful for avoiding hard-coded paths and allows for greater flexibility when changes to routes are required.
Requirements and Installation
We are covering Aura.Router version 1 which requires PHP 5.4+ (the latest version 2 is using PHP 5.3). You can install it in many ways:- Download as a tar ball or zip from GitHub.
- If you are using git, download it via the command line with:
git clone https://github.com/auraphp/Aura.Router.gitOnce you have downloaded Aura.Router, you’ll see a directory with the file structure below: All the source files lie in the
src
directory, and from there follows the PSR-0 Standard for autoloaders. All unit tests reside in the tests
directory; you can run the tests by invoking phpunit
inside the tests
directory (just be sure you have PHPUnit installed).
Working with Aura.Router
A minimal set of mod_rewrite rules need to be written in.htaccess
to point incoming requests to a single entry point.
RewriteEngine On RewriteRule ^$ index.php [QSA] RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php/$1 [QSA,L]The above rules check for existing files and directories and point all other requests to
index.php
.
Or, if you are using php’s built in server, you can run something like the following:
php -S localhost:8080 index.phpThe Aura.Router contains four files in the
src/Aura/Router
folder: Map.php
, Route.php
, RouteFactory.php
and Exception.php
. RouteFactory
is a factory class to create new Route
objects. The RouteFactory
contains a newInstance()
method, which accepts an associative array. The values are passed to the constructor of Route
class.
The Route
object represents an individual route with a name, path, params, values, etc. You should never need to instantiate a Route
directly; you should use RouteFactory
or Map
instead.
The Route
and RouteFactory
accepts an associative array with the following keys:
name
– a string which is the name for the Route.path
– a string which is the path for this Route with param token placeholders.params
– an array of params which map tokens to regex subpatterns.values
– an array of default values for params if none are found.method
– a string or array of HTTP methods; the serverREQUEST_METHOD
must match one of these values.secure
– whether the server must use an HTTPS request.routable
– if true, this route can be matched; if not, it can be used only to generate a path.is_match
– a callable function to evaluate the route.generate
– a callable function to generate a path.name_prefix
– a string prefix for the name.path_prefix
– a string prefix for the path.
Map
class is a collection point of URI routes. Map
’s constructor accepts a RouteFactory
and your routes in a single array of attachment groups. This allows you to separate the configuration and construction of routes.
You can add individual routes to a Map
object via its add()
method, or attach a series of routes using the attach()
method. Which ever way you add a route, all route specifications are stored in the Map
object’s definitions
property which is an array.
Route
objects are only created when you call the generate()
and match()
methods. The order in which the objects are created is the order in which they are defined. It will not create all of the Route
objects for all of the routes defined. If the route name is same it will generate the route with the generate()
method of the Route
object. The match()
method also applies the same way. The match()
method internally calls the Route
object’s isMatch()
method to know whether the routes are same. If there are previous routes created it will traverse them first in that order. If it’s not found in the created routes, it will create the Route
objects for the rest of the routes and look. It’s simple to understand the Map
class as it has only 400 lines of commented code. Feel free to take a look at it for more information.
Basic Usage
The easiest way to create an instance of aMap
object is to require the file instance.php
located in the scripts directory.
<?php
$map = require "/path/to/Aura.Router/scripts/instance.php";
Alternatively, you can create the object manually, which is just what the instance.php
script does anyway.
<?php
use AuraRouterMap;
use AuraRouterDefinitionFactory;
use AuraRouterRouteFactory;
$map = new Map(new DefinitionFactory(), new RouteFactory());
Next, you want to add routes to the object using its add() method.
<?php
// add a simple named route without params
$map->add("home", "/");
// add a simple unnamed route with params
$map->add(null, "/{:controller}/{:action}/{:id:(d+)}");
// add a complex named route
$map->add("read", "/blog/read/{:id}{:format}", [
"params" => [
"id" => "(d+)",
"format" => "(..+)?"],
"values" => [
"controller" => "blog",
"action" => "read",
"format" => "html"
]
]);
The add()
method accepts a route name, path, and associative array. As I mentioned earlier the values contains the default values of the params array. So in the example for the route “read”, you can see the default format is always “html” if none is specified.
Are you wondering why we need a default format? For a REST-like application, the controller and the action will be the same. The rendering of the data differs on the accept format. In this way we don’t need to repeat the same code from one action to another. For example, consider the URIs:
example.com/blog/read/42.html example.com/blog/read/42.json example.com/blog/read/42.atomThe data we need to output is same for each, but in different formats like json, html, and atom. So if none of the formats appear, for example:
example.com/blog/read/42Then it will assume the request is for HTML. For a real REST API, file extensions should not be used to indicate the desired format. Clients should be encouraged to utilize HTTP’s Accept request header. To learn more about REST, you can read the REST – Can You do More than Spell It? series here at SitePoint, or the book REST API Design Rulebook Mark Massé.
Matching a Route
Once the routes have been added, you’ll want to recognize which route has been requested by a user. This is possible with the help thematch()
method of the Map
object. Internally the Map
object is calling the isMatch()
method of the Route
object. For the match method, you need to pass the path and the $_SERVER
value as shown below:
<?php
// get the route
$path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
$route = $map->match($path, $_SERVER);
You may have wondered why we need to pass the path and also the server values. Why can’t Aura.Router get the path itself from the $_SERVER
array you have passed? It’s for some flexibility.
- The
match()
method does not parse the URI or use$_SERVER
internally. This is because different systems may have different ways of representing that information (e.g., through a URI object or a context object). As long as you can pass the string path and a server array, you can use Aura.Router in your application foundation or framework. - Sometimes the URI may be like
http://example.com/base/path/controller/action/param
, and here we have a basepath. So before we look for the match method, we need to remove the basepath. - In some cases you may be getting the
PATH_INFO
fromREDIRECT_PATH_INFO
when your server can understand only/index.php/home/index
, and not by/home/index
.
Route
object, otherwise it returns false. Once you have obtained the object you can access its values as such:
<?php
$route->values["controller"];
$route->values["action"];
$route->values["id"];
It’s from the $route->values
we know what sort of rendering, what sort of class, what sort of method we want to call for dispatching.
Dispatching a Route
If a route is found, it’s easy for you to create the controller object and an appropriate method. This simple example is adapted from the Aura docs:<?php
if (!$route) {
// no route object was returned, You can also set error controller depending on your logic
echo "No application route was found for that URI path.";
exit();
}
// does the route indicate a controller?
if (isset($route->values["controller"])) {
// take the controller class directly from the route
$controller = $route->values["controller"];
}
else {
// use a default controller
$controller = "Index";
}
// does the route indicate an action?
if (isset($route->values["action"])) {
// take the action method directly from the route
$action = $route->values["action"];
}
else {
// use a default action
$action = "index";
}
// instantiate the controller class
$page = new $controller();
// invoke the action method with the route values
echo $page->$action($route->values);
Micro-Framework Routing
Sometimes you may wish to use Aura as a micro-framework. It’s also possible to assigning anonymous function to controller:<?php
$map->add("read", "/blog/read/{:id}{:format}", [
"params" => [
"id" => "(d+)",
"format" => "(..+)?",
],
"values" => [
"controller" => function ($args) {
$id = (int) $args["id"];
return "Reading blog ID {$id}";
},
"format" => ".html",
],
));
When you are using Aura.Router as a micro-framework, the dispatcher will look something similar to the one below:
<?php
$params = $route->values;
$controller = $params["controller"];
unset($params["controller"]);
echo $controller($params);
Generating A Route Path
You may want to generate the route in your view. This is possible with the help of the map’sgenerate()
method. The map generate()
method internally calls the generate()
method of Route
object.
<?php
// $path => "/blog/read/42.atom"
$path = $map->generate("read", [
"id" => 42,
"format" => ".atom"
]);
$href = htmlspecialchars($path, ENT_QUOTES, "UTF-8");
echo '<a href="' . $href . '">Atom feed for this blog entry</a>';
Of course typing “blog/read/42.atom” is shorter, but hardcoded paths are less flexible and makes changing the route more difficult. Consider the following example:
<?php
$map->add("read", "/blog/read/{:id}{:format}", [
"params" => [
"id" => "(d+)",
"format" => "(..+)?"],
"values" => [
"controller" => "blog",
"action" => "read",
"format" => "html"
]
]);
What will happen when you want to change the /blog/read/42.atom
to /article/view/42.atom
? Or, what will happen when your client mentions they want to move to a multilingual website? If you have hard-coded paths, you’ll probably have to change them in many places. The generate()
method always comes handy.
The add()
method’s second argument is an associate array. You can pass callable functions with is_match keys which can either return true or false. And depending on the value, it will return the routes when the match()
method is called. For example:
<?php
$map->add("read", "/blog/read/{:id}{:format}", [
"params" => [
"id" => "(d+)",
"format" => "(..+)?",
],
"values" => [
"controller" => "blog",
"action" => "read",
"id" => 1,
"format" => ".html",
],
"secure" => false,
"method" => ["GET"],
"routable" => true,
"is_match" => function(array $server, ArrayObject $matches) {
// disallow matching if referred from example.com
if ($server["HTTP_REFERER"] == "http://example.com") {
return false;
}
// add the referer from $server to the match values
$matches["referer"] = $server["HTTP_REFERER"];
return true;
},
"generate" => function(AuraRouterRoute $route, array $data) {
$data["foo"] = "bar";
return $data;
}
]);
Here if the HTTP_REFERER
is example.com
we will fail to load the content. You can pass your own callable functions like above. This makes Aura.Router more flexible.
Conclusion
In this article we have covered some of the basic and advanced features of using Aura.Router for web routing. Why not give a try to Aura.Router? I’m sure it will make your life with web routing easier than before. Have a look at http://auraphp.github.com/Aura.Router as it has more to say than we have in the current article. Image via FotoliaFrequently Asked Questions about Web Routing in PHP with Aura Router
What is the Aura Router in PHP?
The Aura Router is a powerful, flexible library for PHP that helps manage web routes. It allows developers to define the routes of a web application and map them to specific HTTP methods and URLs. This makes it easier to handle requests and responses in a structured way. The Aura Router is part of the Aura project, a collection of high-quality, well-tested, standards-compliant, independent library packages for PHP.
How do I install the Aura Router?
The Aura Router can be installed via Composer, a tool for dependency management in PHP. You can install it by running the following command in your terminal: composer require aura/router
. This will download the Aura Router package and its dependencies into your project.
How do I define routes using the Aura Router?
Defining routes with the Aura Router is straightforward. You can use the add
method to add a new route, specifying the route name, path, and HTTP methods. For example: $router->add('home', '/')->addValues(['controller' => 'HomeController', 'action' => 'index']);
. This code defines a route named ‘home’ that responds to the root path (‘/’) and maps to the ‘index’ action of the ‘HomeController’.
How do I handle dynamic routes with the Aura Router?
The Aura Router supports dynamic routes, which are routes with variable parts. You can define a dynamic route by including placeholders in the path. For example: $router->add('user.profile', '/user/{id}');
. This code defines a route that matches URLs like ‘/user/123’, where ‘123’ is a dynamic part that can be any value.
How do I match routes with the Aura Router?
The Aura Router provides a match
method to match a request against the defined routes. This method returns a Route object if a match is found, or false if no match is found. You can use this method to handle requests in your application. For example: $route = $router->match($request);
.
How do I generate URLs with the Aura Router?
The Aura Router provides a generate
method to generate URLs for a given route. This method takes the route name and an array of route parameters as arguments, and returns the generated URL. For example: $url = $router->generate('user.profile', ['id' => 123]);
.
How do I handle 404 errors with the Aura Router?
If the Aura Router cannot match a request to any defined route, it returns false. You can use this to handle 404 errors in your application. For example: $route = $router->match($request); if (!$route) { // Handle 404 error }
.
How do I handle method not allowed errors with the Aura Router?
If the Aura Router matches a request to a route, but the request method is not allowed for that route, it throws a MethodNotAllowedException. You can catch this exception to handle method not allowed errors in your application.
How do I use middleware with the Aura Router?
The Aura Router does not directly support middleware, but you can use it with a middleware dispatcher like Relay. This allows you to add pre-processing and post-processing logic to your routes.
How do I test routes with the Aura Router?
You can test routes with the Aura Router by creating a mock request and matching it against the router. This allows you to verify that your routes are defined correctly and behave as expected.
Hari K T is a Freelance LAMP developer/consultant, open-source contributor auraphp and speaker.