PHPackages                             crysalead/router - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. crysalead/router

ActiveLibrary[HTTP &amp; Networking](/categories/http)

crysalead/router
================

HTTP Request Router

4.0.0(1y ago)1920.2k↓26.9%4[1 issues](https://github.com/crysalead/router/issues)2MITPHPPHP &gt;=7

Since Dec 29Pushed 1y ago3 watchersCompare

[ Source](https://github.com/crysalead/router)[ Packagist](https://packagist.org/packages/crysalead/router)[ RSS](/packages/crysalead-router/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (4)Versions (17)Used By (2)

Router - HTTP Request Router
============================

[](#router---http-request-router)

[![Build Status](https://camo.githubusercontent.com/4ba8b370466b6b640681008d02bf57818494e5b5ef9bcedbc49cc4d9d4d22051/68747470733a2f2f7472617669732d63692e6f72672f63727973616c6561642f726f757465722e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/crysalead/router)[![Code Coverage](https://camo.githubusercontent.com/a2d15508a65a8bbfcaf0821605836f3ea38c820ac51a8044fb4c75d72d52ef4e/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f63727973616c6561642f726f757465722f6261646765732f636f7665726167652e706e673f623d6d6173746572)](https://scrutinizer-ci.com/g/crysalead/router/)

[Complete benchmark results can be found here.](https://github.com/jails/php-router-benchmark)

- Compatible with PSR-7
- Named routes
- Reverses routing
- Sub-domain
- Nested routes
- Custom dispatching strategy
- Advanced route pattern syntax

Installation
------------

[](#installation)

```
composer require crysalead/router
```

API
---

[](#api)

### Route patterns

[](#route-patterns)

Route pattern are path string with curly brace placeholders. Possible placeholder format are:

- `'{name}'` - placeholder
- `'{name:regex}'` - placeholder with regex definition.
- `'[{name}]'` - optionnal placeholder
- `'[{name}]+'` - recurring placeholder
- `'[{name}]*'` - optionnal recurring placeholder

Variable placeholders may contain only word characters (latin letters, digits, and underscore) and must be unique within the pattern. For placeholders without an explicit regex, a variable placeholder matches any number of characters other than '/' (i.e `[^/]+`).

You can use square brackets (i.e `[]`) to make parts of the pattern optional. For example `/foo[bar]` will match both `/foo` and `/foobar`. Optional parts can be nested and repeatable using the `[]*` or `[]+` syntax. Example: `/{controller}[/{action}[/{args}]*]`.

Examples:

- `'/foo/'` - Matches only if the path is exactly '/foo/'. There is no special treatment for trailing slashes, and patterns have to match the entire path, not just a prefix.
- `'/user/{id}'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/bob/details' but not '/user/' or '/user'.
- `'/user/{id:[^/]+}'` - Same as the previous example.
- `'/user[/{id}]'` - Same as the previous example, but also match '/user'.
- `'/user[/[{id}]]'` - Same as the previous example, but also match '/user/'.
- `'/user[/{id}]*'` - Match '/user' as well as 'user/12/34/56'.
- `'/user/{id:[0-9a-fA-F]{1,8}}'` - Only matches if the id parameter consists of 1 to 8 hex digits.
- `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the path into the parameter 'path'.

Note: the difference between `/{controller}[/{action}[/{args}]*]` and `/{controller}[/{action}[/{args:.*}]]` for example is `args` will be an array using `[/{args}]*` while a unique "slashed" string using `[/{args:.*}]`.

### The Router

[](#the-router)

The `Router` instance can be instantiated so:

```
use Lead\Router\Router;

$router = new Router();
```

Optionally, if your project lives in a sub-folder of your web root you'll need to set a base path using `basePath()`. This base path will be ignored so your routes won't need to be prefixed with it to matches the request path.

```
$router->basePath('/my/sub/dir');
```

Note: If you are using the [crysalead/net](https://github.com/crysalead/net) library you can pass `Request::ingoing()->basePath();` directly so you won't need to set it manually.

#### The Router Public Methods

[](#the-router-public-methods)

```
$router->basePath();   // Gets/sets the router base path
$router->group();      // To create some scoped routes
$router->bind();       // To create a route
$router->route();      // To route a request
$router->link();       // To generate a route's link
$router->apply();      // To add a global middleware
$router->middleware(); // The router's middleware generator
$router->strategy();   // Gets/sets a routing strategy
```

### Route definition

[](#route-definition)

Example of routes definition:

```
use Lead\Router\Router;

$router = new Router();

$router->bind($pattern, $handler);                                 // route matching any request method
$router->bind($pattern, $options, $handler);                       // alternative syntax with some options.
$router->bind($pattern, ['methods' => 'GET'], $handler);           // route matching on only GET requests
$router->bind($pattern, ['methods' => ['POST', 'PUT']], $handler); // route matching on POST and PUT requests

// Alternative syntax
$router->get($pattern, $handler);    // route matching only get requests
$router->post($pattern, $handler);   // route matching only post requests
$router->delete($pattern, $handler); // route matching only delete requests
```

In the above example a route is registered using the `->bind()` method and takes as parametters a route pattern, an optionnal options array and the callback handler.

The second parameter is an `$options` array where possible values are:

- `'scheme'`: the scheme constraint (default: `'*'`)
- `'host'`: the host constraint (default: `'*'`)
- `'methods'`: the method constraint (default: `'*'`)
- `'name'`: the name of the route (optional)
- `'namespace'`: the namespace to attach to a route (optional)

The last parameter is the callback handler which contain the dispatching logic to execute when a route matches the request. The callback handler is the called with the matched route as first parameter and the response object as second parameter:

```
$router->bind('foo/bar', function($route, $response) {
});
```

#### The Route Public Attributes

[](#the-route-public-attributes)

```
$route->method;       // The method contraint
$route->params;       // The matched params
$route->persist;      // The persisted params
$route->namespace;    // The namespace
$route->name;         // The route's name
$route->request;      // The routed request
$route->response;     // The response (same as 2nd argument, can be `null`)
$route->dispatched;   // To store the dispated instance if applicable.
```

#### The Route Mublic Methods

[](#the-route-mublic-methods)

```
$route->host();       // The route's host instance
$route->pattern();    // The pattern
$route->regex();      // The regex
$route->variables();  // The variables
$route->token();      // The route's pattern token structure
$route->scope();      // The route's scope
$route->error();      // The route's error number
$route->message();    // The route's error message
$route->link();       // The route's link
$route->apply();      // To add a new middleware
$route->middleware(); // The route's middleware generator
$route->handler();    // The route's handler
$route->dispatch();   // To dispatch the route (i.e execute the route's handler)
```

### Named Routes And Reverse Routing

[](#named-routes-and-reverse-routing)

To be able to do some reverse routing, route must be named using the following syntax first:

```
$route = $router->bind('foo/{bar}', ['name' => 'foo'], function() { return 'hello'; });
```

Named routes can be retrieved using the array syntax on the router instance:

```
$router['foo']; // Returns the `'foo'` route.
```

Once named, the reverse routing can be done using the `->link()` method:

```
echo $router->link('foo', ['bar' => 'baz']); // /foo/baz
```

The `->link()` method takes as first parameter the name of a route and as second parameter the route's arguments.

### Grouping Routes

[](#grouping-routes)

It's possible to apply a scope to a set of routes all together by grouping them into a dedicated group using the `->group()` method.

```
$router->group('admin', ['namespace' => 'App\Admin\Controller'], function($router) {
    $router->bind('{controller}[/{action}]', function($route, $response) {
        $controller = $route->namespace . ucfirst($route->params['controller']);
        $instance = new $controller($route->params, $route->request, $route->response);
        $action = isset($route->params['action']) ? $route->params['action'] : 'index';
        $instance->{$action}();
        return $route->response;
    });
});
```

The above example will be able to route `/admin/user/edit` on `App\Admin\Controller\User::edit()`. The fully-namespaced class name of the controller is built using the `{controller}` variable and it's then instanciated to process the request by running the `{action}` method.

### Sub-Domain And/Or Prefix Routing

[](#sub-domain-andor-prefix-routing)

To supports some sub-domains routing, the easiest way is to group routes using the `->group()` method and setting up the host constraint like so:

```
$router->group(['host' => 'foo.{domain}.bar'], function($router) {
    $router->group('admin', function($router) {
        $router->bind('{controller}[/{action}]', function() {});
    });
});
```

The above example will be able to route `http://foo.hello.bar/admin/user/edit` for example.

### Middleware

[](#middleware)

Middleware functions are functions that have access to the request object, the response object, and the next middleware function in the application’s request-response cycle. Middleware functions provide the same level of control as aspects in [AOP](https://en.wikipedia.org/wiki/Aspect-oriented_programming). It allows to:

- Execute any code.
- Make changes to the request and the response objects.
- End the request-response cycle.
- Call the next middleware function in the stack.

And it's also possible to apply middleware functions globally on a single route or on a group of them. Adding a middleware to a Route is done using the `->apply()` method:

```
$mw = function ($request, $response, $next) {
    return 'BEFORE' . $next($request, $response) . 'AFTER';
};

$router->get('foo', function($route) {
    return '-FOO-';
})

echo $router->route('foo')->dispatch($response); //BEFORE-FOO-AFTER
```

You can also attach middlewares on groups.

```
$mw1 = function ($request, $response, $next) {
    return '1' . $next($request, $response) . '1';
};
$mw2 = function ($request, $response, $next) {
    return '2' . $next($request, $response) . '2';
};
$mw3 = function ($request, $response, $next) {
    return '3' . $next($request, $response) . '3';
};
$router->apply($mw1); // Global

$router->group('foo', function($router) {
    $router->get('bar', function($route) {
        return '-BAR-';
    })->apply($mw3);  // Local
})->apply($mw2);      // Group

echo $router->route('foo/bar')->dispatch($response); //321-BAR-123
```

### Dispatching

[](#dispatching)

Dispatching is the outermost layer of the framework, responsible for both receiving the initial HTTP request and sending back a response at the end of the request's life cycle.

This step has the responsibility to loads and instantiates the correct controller, resource or class to build a response. Since all this logic depends on the application architecture, the dispatching has been splitted in two steps for being as flexible as possible.

#### Dispatching A Request

[](#dispatching-a-request)

The URL dispatching is done in two steps. First the `->route()` method is called on the router instance to find a route matching the URL. The route accepts as arguments:

- An instance of `Psr\Http\Message\RequestInterface`
- An url or path string
- An array containing at least a path entry
- A list of parameters with the following order: path, method, host and scheme

The `->route()` method returns a route (or a "not found" route), then the `->dispatch()` method will execute the dispatching logic contained in the route handler (or throwing an exception for non valid routes).

```
use Lead\Router\Router;

$router = new Router();

// Bind to all methods
$router->bind('foo/bar', function() {
    return "Hello World!";
});

// Bind to POST and PUT at dev.example.com only
$router->bind('foo/bar/edit', ['methods' => ['POST',' PUT'], 'host' => 'dev.example.com'], function() {
    return "Hello World!!";
});

// The Router class makes no assumption of the ingoing request, so you have to pass
// uri, methods, host, and protocol into `->route()` or use a PSR-7 Compatible Request.
// Do not rely on $_SERVER, you must check or sanitize it!
$route = $router->route(
    $_SERVER['REQUEST_URI'], // foo/bar
    $_SERVER['REQUEST_METHOD'], // get, post, put...etc
    $_SERVER['HTTP_HOST'], // www.example.com
    $_SERVER['SERVER_PROTOCOL'] // http or https
);

echo $route->dispatch(); // Can throw an exception if the route is not valid.
```

#### Dispatching A Request Using Some PSR-7 Compatible Request/Response

[](#dispatching-a-request-using-some-psr-7-compatible-requestresponse)

It also possible to use compatible Request/Response instance for the dispatching.

```
use Lead\Router\Router;
use Lead\Net\Http\Cgi\Request;
use Lead\Net\Http\Response;

$request = Request::ingoing();
$response = new Response();

$router = new Router();
$router->bind('foo/bar', function($route, $response) {
    $response->body("Hello World!");
    return $response;
});

$route = $router->route($request);

echo $route->dispatch($response); // Can throw an exception if the route is not valid.
```

#### Handling dispatching failures

[](#handling-dispatching-failures)

```
use Lead\Router\RouterException;
use Lead\Router\Router;
use Lead\Net\Http\Cgi\Request;
use Lead\Net\Http\Response;

$request = Request::ingoing();
$response = new Response();

$router = new Router();
$router->bind('foo/bar', function($route, $response) {
    $response->body("Hello World!");
    return $response;
});

$route = $router->route($request);

try {
    echo $route->dispatch($response);
} catch (RouterException $e) {
    http_response_code($e->getCode());
    // Or you can use Whoops or whatever to render something
}
```

### Setting up a custom dispatching strategy.

[](#setting-up-a-custom-dispatching-strategy)

To use your own strategy you need to create it using the `->strategy()` method.

Bellow an example of a RESTful strategy:

```
use Lead\Router\Router;
use My\Custom\Namespace\ResourceStrategy;

Router::strategy('resource', new ResourceStrategy());

$router = new Router();
$router->resource('Home', ['namespace' => 'App\Resource']);

// Now all the following URL can be routed
$router->route('home');
$router->route('home/123');
$router->route('home/add');
$router->route('home', 'POST');
$router->route('home/123/edit');
$router->route('home/123', 'PATCH');
$router->route('home/123', 'DELETE');
```

The strategy:

```
namespace use My\Custom\Namespace;

class ResourceStrategy {

    public function __invoke($router, $resource, $options = [])
    {
        $path = strtolower(strtr(preg_replace('/(?
