Routing refers to the process of accepting user-friendly URLs, dissecting the URL into its component parts, and then making a determination as to which class and method should be dispatched. The advantage of such an implementation is that not only can you make your URLs Search Engine Optimization (SEO)-friendly, but you can also create rules, incorporating regular expression patterns, which can extract values of parameters.
mod_rewrite. You then define rewriting rules that allow graphic file requests and requests for CSS and JavaScript to pass untouched. Otherwise, the request would be funneled through a routing method.home, page, and the default. The default should be last as it will match anything not matched previously. The action is in the form of an anonymous function that will be executed if a route match occurs:$config = [
'home' => [
'uri' => '!^/$!',
'exec' => function ($matches) {
include PAGE_DIR . '/page0.php'; }
],
'page' => [
'uri' => '!^/(page)/(\d+)$!',
'exec' => function ($matches) {
include PAGE_DIR . '/page' . $matches[2] . '.php'; }
],
Router::DEFAULT_MATCH => [
'uri' => '!.*!',
'exec' => function ($matches) {
include PAGE_DIR . '/sorry.php'; }
],
];Router class. We first define constants and properties that will be of use during the process of examining and matching a route:namespace Application\Routing;
use InvalidArgumentException;
use Psr\Http\Message\ServerRequestInterface;
class Router
{
const DEFAULT_MATCH = 'default';
const ERROR_NO_DEF = 'ERROR: must supply a default match';
protected $request;
protected $requestUri;
protected $uriParts;
protected $docRoot;
protected $config;
protected $routeMatch;ServerRequestInterface compliant class, the path to the document root, and the configuration file mentioned earlier. Note that we throw an exception if the default configuration is not supplied:public function __construct(ServerRequestInterface $request, $docRoot, $config)
{
$this->config = $config;
$this->docRoot = $docRoot;
$this->request = $request;
$this->requestUri =
$request->getServerParams()['REQUEST_URI'];
$this->uriParts = explode('/', $this->requestUri);
if (!isset($config[self::DEFAULT_MATCH])) {
throw new InvalidArgumentException(
self::ERROR_NO_DEF);
}
}public function getRequest()
{
return $this->request;
}
public function getDocRoot()
{
return $this->docRoot;
}
public function getRouteMatch()
{
return $this->routeMatch;
}isFileOrDir() method is used to determine whether we are trying to match against a CSS, JavaScript, or graphic request (among other possibilities):public function isFileOrDir()
{
$fn = $this->docRoot . '/' . $this->requestUri;
$fn = str_replace('//', '/', $fn);
if (file_exists($fn)) {
return $fn;
} else {
return '';
}
}match(), which iterates through the configuration array and runs the uri parameter through preg_match(). If positive, the configuration key and $matches array populated by preg_match() are stored in $routeMatch, and the callback is returned. If there is no match, the default callback is returned:public function match()
{
foreach ($this->config as $key => $route) {
if (preg_match($route['uri'],
$this->requestUri, $matches)) {
$this->routeMatch['key'] = $key;
$this->routeMatch['match'] = $matches;
return $route['exec'];
}
}
return $this->config[self::DEFAULT_MATCH]['exec'];
}
}First, change to /path/to/source/for/this/chapter and create a directory called routing. Next, define a file, index.php, which sets up autoloading and uses the right classes. You can define a constant PAGE_DIR that points to the pages directory created in the previous recipe:
<?php
define('DOC_ROOT', __DIR__);
define('PAGE_DIR', DOC_ROOT . '/../pages');
require_once __DIR__ . '/../../Application/Autoload/Loader.php';
Application\Autoload\Loader::init(__DIR__ . '/../..');
use Application\MiddleWare\ServerRequest;
use Application\Routing\Router;Next, add the configuration array discussed in step 3 of this recipe. Note that you could add (/)? at the end of the pattern to account for an optional trailing slash. Also, for the home route, you could offer two options: either / or /home:
$config = [
'home' => [
'uri' => '!^(/|/home)$!',
'exec' => function ($matches) {
include PAGE_DIR . '/page0.php'; }
],
'page' => [
'uri' => '!^/(page)/(\d+)(/)?$!',
'exec' => function ($matches) {
include PAGE_DIR . '/page' . $matches[2] . '.php'; }
],
Router::DEFAULT_MATCH => [
'uri' => '!.*!',
'exec' => function ($matches) {
include PAGE_DIR . '/sorry.php'; }
],
];You can then define a router instance, supplying an initialized ServerRequest instance as the first argument:
$router = new Router((new ServerRequest()) ->initialize(), DOC_ROOT, $config); $execute = $router->match(); $params = $router->getRouteMatch()['match'];
You then need to check to see whether the request is a file or directory, and also whether the route match is /:
if ($fn = $router->isFileOrDir()
&& $router->getRequest()->getUri()->getPath() != '/') {
return FALSE;
} else {
include DOC_ROOT . '/main.php';
}Next, define main.php, something like this:
<?php // demo using middleware for routing ?>
<!DOCTYPE html>
<head>
<title>PHP 7 Cookbook</title>
<meta http-equiv="content-type"
content="text/html;charset=utf-8" />
</head>
<body>
<?php include PAGE_DIR . '/route_menu.php'; ?>
<?php $execute($params); ?>
</body>
</html>And finally, a revised menu that uses user-friendly routing is required:
<?php // menu for routing ?> <a href="/home">Home</a> <a href="/page/1">Page 1</a> <a href="/page/2">Page 2</a> <a href="/page/3">Page 3</a> <!-- etc. -->
To test the configuration using Apache, define a virtual host definition that points to /path/to/source/for/this/chapter/routing. In addition, define a .htaccess file that directs any request that is not a file, directory, or link to index.php. Alternatively, you could just use the built-in PHP webserver. In a terminal window or command prompt, type this command:
cd /path/to/source/for/this/chapter/routing php -S localhost:8080
In a browser, the output when requesting http://localhost:8080/home is something like this:

For information on rewriting using the NGINX web server, have a look at this article: http://nginx.org/en/docs/http/ngx_http_rewrite_module.html. There are plenty of sophisticated PHP routing libraries available that introduce far greater functionality than the simple router presented here. These include Altorouter (http://altorouter.com/), TreeRoute (https://github.com/baryshev/TreeRoute), FastRoute (https://github.com/nikic/FastRoute), and Aura.Router. (https://github.com/auraphp/Aura.Router). In addition, most frameworks (for example, Zend Framework 2 or CodeIgniter) have their own routing capabilities.