PHPackages                             mattferris/http-routing - 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. mattferris/http-routing

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

mattferris/http-routing
=======================

A lightweight, flexible HTTP request router

1.0(6y ago)0167[1 issues](https://github.com/mattferris/http-routing/issues)1BSD-2-ClausePHPCI failing

Since May 5Pushed 6y ago1 watchersCompare

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

READMEChangelog (4)Dependencies (5)Versions (13)Used By (1)

HTTP Routing
============

[](#http-routing)

[![Build Status](https://camo.githubusercontent.com/c1d6ebbcf432b3b6d586853df5e7aa7d186c99d359d40b376fa1f585f5994599/68747470733a2f2f7472617669732d63692e6f72672f6d6174746665727269732f687474702d726f7574696e672e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/mattferris/http-routing)[![SensioLabsInsight](https://camo.githubusercontent.com/43cb51bd2ce1319953fe3ae4ff400b09922a74086dc7827d88bf62d403ef5ba1/68747470733a2f2f696e73696768742e73656e73696f6c6162732e636f6d2f70726f6a656374732f32393936663365382d623761392d343234632d613635362d3933396539386430373931362f6d696e692e706e67)](https://insight.sensiolabs.com/projects/2996f3e8-b7a9-424c-a656-939e98d07916)

A PSR-7 compliant HTTP routing library for PHP

Installable via composer:

```
composer require mattferris/http-routing

```

To use this library, you'll need to also install a PSR-7 compliant HTTP messaging library like [zendframework/zend-diactoros](https://github.com/zendframework/zend-diactoros).

Dispatcher
----------

[](#dispatcher)

The dispatcher resolves requests by comparing the passed instance `Psr\Http\Message\ServerRequestInterface` against it's list of routes.

```
use MattFerris\Http\Routing\Dispatcher;

$dispatcher = new Dispatcher($request);
```

Calling `dispatch()` routes the request to an action. The action is responsible for generating and returning a response. This response is then returned by `dispatch()`. The response is an instance of `Psr\Http\Message\ResponseInterface`.

```
// get a response
$response = $dispatcher->dispatch($request);
```

Routing
-------

[](#routing)

Routes define the criteria that a request must match in order for a given action to process the request. Actions must be `callable` or a string with a class name and a method name separated by a colon (:). Using the `ClassName:methodName`syntax has the advantage of letting the controller worry about instantiating the controller (allowing the controller to have dependecies injected).

Routes are evaluated in the order they are added. Multiple actions can process a single request, with processing continuing until an action returns a response. The collection of routes is referred to as a *route stack*.

Routes can be added a number of ways. A simple method is by using the helper methods, named after the HTTP methods you want to match (`get()`, `post()`, `put()`, `delete()`, `head()`, `options()` and `trace()`).

```
// handle requests for GET /foo with a closure
$dispatcher->get('/foo', function () {
    return $response;
});

// handle requests for POST /foo with the fooAction() method on the Controller class
$dispatcher->post('/foo', 'Controller::fooAction');
```

You can also match any HTTP method using `any()`.

```
// handle requests for /foo with fooAction() method on $object
$dispatcher->any('/foo', array($object, 'fooAction'));
```

All helper methods also support a third parameter for matching HTTP headers. The headers must be passed an array with the array keys as the header name. Header names are not case-sensitive.

```
$dispatcher->get('/foo', 'Controller::fooAction', array(
    'Host' => 'example.com'
));
```

### Capturing Parameters

[](#capturing-parameters)

You can capture parts of the URI using named parameters. For example, to capture usernames in the URI `/users/joe`, where `joe` can be any username, you could do the following.

```
$dispatcher->get('/users/{username}', 'Controller::fooAction');
```

The value captured by the parameter is passed to the action as an argument.

```
class Controller
{
    public function fooAction($username)
    {
        ...
    }
}
```

Multiple parameters can be captured and will be passed to the action in the same way.

```
$dispatcher->get('/users/{username}/{option}', function ($username, $option) {
    // ...
});

```

Parameter names must start with letters or an underscore and contain letters, numbers and underscores (as a regex, this would look like `[a-zA-Z_][a-zA-Z0-9_]+`). This follows PHPs allowed characters for variable names.

By default, all parameters are required for a URI to match the route. When a route is created, you can define default values for it's parameters. Any parameter that has a default value is considered to be optional. Parameters are passed as the fourth argument.

```
$dispatcher->get('/users/{username}/{option}', $action, $headers, ['option' => 'update']);
```

This route will now match `/users/joe/preferences` as well as `/users/joe/`. In the case of `/users/joe/`, because `{option}` isn't specified it will default to `update`.

Once a parameter has been defined as optional, all parameters that follow must also be optional. A `BadLogicException` will be thrown if a required parameter proceeds an optional parameter.

```
// {details} must be optional because {option} is optional
$dispatcher->get('/users/{username}/{option}/{details}', $action, $headers, ['option' => 'update', 'details' => '']);

// {details} is required (no default value), which will throw an exception
$dispatcher->get('/users/{username}/{option}/{details}', $action $headers, ['option' => 'update']);
```

### Error 404 Handling

[](#error-404-handling)

You can define a 404 handler easily enough by defining the last route in the *route stack* with generic criteria, and setting the action to a piece of code that can generate an approriate response.

```
$dispatcher->any('/', function () {
    return $error404Response;
});
```

Actions
-------

[](#actions)

An action defines the code that actually processes the request and generates a response. The only requirement of an action is that it return an instance of `Psr\Http\Message\ResponseInterface`, an instance of `Psr\Http\Message\ServerRequestInterface`, or nothing at all.

```
// given these routes...
$dispatcher
    ->get('/foo/{bar}', 'MyController::getFooAction')
    ->post('/foo', 'MyController::postFooAction');

// your controller might look like...
class MyController
{
    public function getFooAction($bar)
    {
        ...

        return $response;
    }

    public function postFooAction()
    {
        ...

        return $response;
    }
}
```

### Internal Redirects

[](#internal-redirects)

You can redirect a client with an HTTP 301 response (for example), which the browser then interprets and issues a new request to the specified URL. In some cases, you may want to simply re-evaluate a new request without returning anything to the client. This is possible by returning an instance of `Psr\Http\Message\ServerRequestInterface` from the action.

```
public function someAction()
{
    return $request;
}
```

When `Dispatcher` identifies the return value from the action as a new request, it calls `dispatch()` again and passes the new request as an argument. The new request is processed exactly the same as the original.

### Fall-Through Routes

[](#fall-through-routes)

*Fall-through routes* are routes which don't return a response, and therefore allow further matching to continue. They can be useful for executing code without terminating the routing process. For example, you could use a *fall-through route* to add request logging.

```
$dispatcher->any('/', function (Request $request) {
    error_log('received request: '.$request->getUri());
});
```

### Argument Injection

[](#argument-injection)

The routing section touched on how named parameters can be accessed via the arguments of your action, i.e. a pattern named `username` can be access via an argument name `$username`. This is done via injection, where the dependency injector matches the argument name to the parameter. In addition to parameters, your actions can access additional information via arguments, such as the current request object.

```
public function someAction(\Psr\Http\Message\ServerRequestInterface $request)
{
    // ...
}
```

For more on dependency injection, checkout [mattferris/di](http://bueller.ca/di).

Named Routes and Reverse Routing
--------------------------------

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

You can optionally specify names for routes. Named routes can then be used to generate URIs that will match the given route using `generate($name)`. Use this method of URI generation to bind your application to routes instead of URIs.

```
// specify a route called auth_login
$dispatcher->post('/login', 'AuthController::login', [], 'auth_login');

// now you can generate URIs in your actions based on the route
public function someAction()
{
    $uri = $dispatcher->generate('auth_login');
    // $uri contains '/login'
}

// if you need to change the route, the action will automatically
// generate the correct URI
$dispatcher->post('/auth/login', 'AuthController::login', [], 'auth_login');
```

### Parameters

[](#parameters)

Routes containing parameters must be passed an array of parameter values to use.

```
$dispatcher->get('/users/{user}', 'UsersController::getUser', [], 'get_user');

// pass the username to use for the {user} parameter
$uri = $dispatcher->generate('get_user', ['user' => 'joe']);
echo $uri; // outputs '/users/joe'
```

You can pass extra parameters that aren't defined in the route. Extra parameters are used to generate a query string.

```
$uri = $dispatcher->generate('get_user', ['user' => 'joe', 'foo' => 'bar']);
echo $uri; // outputs '/users/joe?foo=bar'
```

Route Providers
---------------

[](#route-providers)

Within your application, you can define *providers*, which provide a collection of routes that parts of your application can handle. *Providers* can be registered with a dispatcher via `register()`. *Providers* are just a plain class implementing `MattFerris\Provider\ProviderInterface`, and must define a single method, `provides()`, which accepts an instance of `Dispatcher` as it's only argument.

```
class RoutingProvider implements \MattFerris\Provider\ProviderInterface
{
    public function provides($consumer)
    {
        $dispatcher->get('/users/{username}', 'Controller::someAction', ['Host => 'example.com']);
    }
}

$dispatcher->register(new RoutingProvider());
```

Providers offer a convenient way of allowing parts of your application to manage their own routing.

Advanced Routing
----------------

[](#advanced-routing)

### Additional Route Types

[](#additional-route-types)

Internally, routes are represented as objects implementing `RouteInterface`. When adding routes using the helper methods, the `Dispatcher` creates route objects. By default, these route objects are all `PathRoute` instances, as `PathRoute` is the default type. This can be changed by calling `Dispatcher::setDefaultRouteType()`. Two other route types are included: `SimpleRoute` and `RegexRoute`.

```
$dispatcher->setDefaultRouteType('\MattFerris\Http\Routing\RegexRoute');
```

After setting the new default route type, all helper methods will then create instances of the new route type. This can be used to implement your own route type.

You can also add route objects directly using `Dispatcher::add()` and `Dispatcher::insert()`. `add()` adds the route to the end of the route stack, while `insert()` allows you to insert the route into any position in the route stack. This can be useful for adding early routes to capture requests for middleware to process. `add()` and `insert()` accept instances of `RouteInterface`.

```
$dispatcher
    ->add(new SimpleRoute('/foo', 'Controller::someAction'))
    ->insert(new RegexRoute('/foo/(bar|baz)', 'Controller::anotherAction'));
```

Route constructors accept 4 arguments.

```
new SimpleRoute($uri, $action, $method, $headers);
```

`$method` and `$headers` are optional.

`RegexRoute` allows you to use regular expressions to match URIs, methods and headers. While flexible, the syntax for capturing parameters can be a little unweildly (`/users/(?P[a-zA-Z_]+?)/`). `PathRoute` extends `RegexRoute` to provide friendly parameter matching for URIs, but can still employ full regex functionality for URIs, methods and headers as well.

`SimpleRoute` truly is simple. Not pattern matching. URIs are matched on prefix, so `/foo` will match `/foo/bar` and `/foo/baz`. It's sole purpose is for efficiency.

Route types can be used together within the same route stack to acheive effeciency or flexibility where it's needed most.

```
// capture requests for logging middleware
$dispatcher->add(new SimpleRoute('/', 'LoggerController::logRequest'));

// process user requests
$dispatcher->add(new PathRoute('/users/{username}', 'UsersController::getUser', 'GET');

// capture similar requests
$dispatcher->add(new RegexRoute('/(help|support)', 'HelpController::getHelp', 'GET');

// error 404
$dispatcher->add(new SimpleRoute('/', 'ErrorController::error404');
```

### RegexRoute and Optional Parameters

[](#regexroute-and-optional-parameters)

When specifying default parameters in `RegexRoute` routes, the optional parameter sub-patterns must allow for an empty match.

```
new RegexRoute('/users/(?[a-zA-Z_]+?|)', $action, $method, $headers, ['username' => '']);
```

###  Health Score

31

—

LowBetter than 68% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity11

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity70

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~173 days

Recently: every ~388 days

Total

11

Last Release

2296d ago

Major Versions

0.7.2 → 1.02020-02-04

### Community

Maintainers

![](https://www.gravatar.com/avatar/9d3d46e7fdc93a3d67119773bdae779e281a87f2d70e95568a969c72205aedf7?d=identicon)[mattferris](/maintainers/mattferris)

---

Top Contributors

[![mattferris](https://avatars.githubusercontent.com/u/1687325?v=4)](https://github.com/mattferris "mattferris (96 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/mattferris-http-routing/health.svg)

```
[![Health](https://phpackages.com/badges/mattferris-http-routing/health.svg)](https://phpackages.com/packages/mattferris-http-routing)
```

###  Alternatives

[league/uri-interfaces

Common tools for parsing and resolving RFC3987/RFC3986 URI

536204.9M23](/packages/league-uri-interfaces)[shopify/shopify-api

Shopify API Library for PHP

4634.8M16](/packages/shopify-shopify-api)[laudis/neo4j-php-client

Neo4j-PHP-Client is the most advanced PHP Client for Neo4j

184616.9k31](/packages/laudis-neo4j-php-client)[http-interop/response-sender

A function to convert PSR-7 Response to HTTP output

46711.5k40](/packages/http-interop-response-sender)[phpro/http-tools

HTTP tools for developing more consistent HTTP implementations.

28137.8k](/packages/phpro-http-tools)[mezzio/mezzio-authentication-oauth2

OAuth2 (server) authentication middleware for Mezzio and PSR-7 applications.

28483.0k2](/packages/mezzio-mezzio-authentication-oauth2)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
