PHPackages                             ingenioz-it/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. [Framework](/categories/framework)
4. /
5. ingenioz-it/router

ActiveLibrary[Framework](/categories/framework)

ingenioz-it/router
==================

A PHP router

v2.0.3(2y ago)140MITPHPPHP &gt;=8.2

Since Dec 13Pushed 2y ago1 watchersCompare

[ Source](https://github.com/IngeniozIT/router)[ Packagist](https://packagist.org/packages/ingenioz-it/router)[ RSS](/packages/ingenioz-it-router/feed)WikiDiscussions master Synced today

READMEChangelog (6)Dependencies (12)Versions (7)Used By (0)

Router
======

[](#router)

A PHP Router.

Disclaimer
----------

[](#disclaimer)

In order to ensure that this package is easy to integrate into your app, it is built around the **PHP Standard Recommendations** : it takes in a [PSR-7 Server Request](https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface) and returns a [PSR-7 Response](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface). It also uses a [PSR-11 Container](https://www.php-fig.org/psr/psr-11/) (such as [EDICT](https://github.com/IngeniozIT/psr-container-edict)) to resolve the route handlers.

It is inspired by routers from well-known frameworks *(did anyone say Laravel ?)* aswell as some home-made routers used internally by some major companies.

It is build with quality in mind : readability, immutability, no global states, 100% code coverage, 100% mutation testing score, and validation from various static analysis tools at the highest level.

About
-----

[](#about)

InfoValueLatest release[![Packagist Version](https://camo.githubusercontent.com/035ffdc9dc8e868dd2263abbe6ad73846132453693cb76d017803e328c8438fe/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f696e67656e696f7a2d69742f726f75746572)](https://packagist.org/packages/ingenioz-it/router)Requires[![PHP from Packagist](https://camo.githubusercontent.com/f539ce7d738055c883904660f54585556742d0cacd7a57979b0ce4154c2eda07/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f696e67656e696f7a2d69742f726f757465722e737667)](https://camo.githubusercontent.com/f539ce7d738055c883904660f54585556742d0cacd7a57979b0ce4154c2eda07/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f696e67656e696f7a2d69742f726f757465722e737667)License[![Packagist](https://camo.githubusercontent.com/05105de6ac4e7bf1f5ca501cafc27c5da9ca02afbc9601298b5f7d022ad7f128/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f696e67656e696f7a2d69742f726f75746572)](https://camo.githubusercontent.com/05105de6ac4e7bf1f5ca501cafc27c5da9ca02afbc9601298b5f7d022ad7f128/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f696e67656e696f7a2d69742f726f75746572)Unit tests[![tests](https://github.com/IngeniozIT/router/actions/workflows/1-tests.yml/badge.svg)](https://github.com/IngeniozIT/router/actions/workflows/1-tests.yml)Code coverage[![Code Coverage](https://camo.githubusercontent.com/f4081698d9c678df0cc43c92469fc06dc1f72bf0f4bb1d3365f1eb0390dab8f3/68747470733a2f2f636f6465636f762e696f2f67682f496e67656e696f7a49542f726f757465722f6272616e63682f6d61737465722f67726170682f62616467652e737667)](https://codecov.io/gh/IngeniozIT/router)Code quality[![code-quality](https://github.com/IngeniozIT/router/actions/workflows/2-code-quality.yml/badge.svg)](https://github.com/IngeniozIT/router/actions/workflows/2-code-quality.yml)Quality tested with[phpunit](https://github.com/sebastianbergmann/phpunit), [phan](https://github.com/phan/phan), [psalm](https://github.com/vimeo/psalm), [phpcs](https://github.com/squizlabs/PHP_CodeSniffer), [phpstan](https://github.com/phpstan/phpstan), [phpmd](https://github.com/phpmd/phpmd), [infection](https://github.com/infection/infection), [rector](https://github.com/rectorphp/rector)Installation
------------

[](#installation)

```
composer require ingenioz-it/router
```

Documentation
-------------

[](#documentation)

### Overview

[](#overview)

Here is the whole process of using this router :

- Create your routes
- Instantiate the router
- Handle the request:

```
use IngeniozIT\Router\RouteGroup;
use IngeniozIT\Router\Route;
use IngeniozIT\Router\Router;

// Create your routes

$routes = new RouteGroup([
    Route::get('/hello', fn() => new Response('Hello, world!')),
    Route::get('/bye', fn() => new Response('Goodbye, world!')),
]);

// Instantiate the router

/** @var Psr\Container\ContainerInterface $container */
$container = new Container();
$router = new Router($routes, $container);

// Handle the request

/** @var Psr\Http\Message\ServerRequestInterface $request */
$request = new ServerRequest();
/** @var Psr\Http\Message\ResponseInterface $response */
$response = $router->handle($request);
```

### Basic routing

[](#basic-routing)

The simplest route consists of a path and a handler.

The path is a string, and the handler is a callable that will be executed when the route is matched. The handler must return a PSR-7 ResponseInterface.

```
Route::get('/hello', fn() => new Response('Hello, world!'));
```

### Organizing routes

[](#organizing-routes)

Route groups are used to contain routes definitions.
They also allows you to visually organize your routes according to your application's logic.

This is useful when you want to apply the same conditions, middlewares, or attributes to several routes at once (as we will see later).

```
new RouteGroup([
    Route::get('/hello', fn() => new Response('Hello, world!')),
    Route::get('/bye', fn() => new Response('Goodbye, world!')),
]);
```

Route groups can be nested to create a hierarchy of routes that will inherit everything from their parent groups.

```
new RouteGroup([
    Route::get('/', fn() => new Response('Welcome !')),
    new RouteGroup([
        Route::get('/hello', fn() => new Response('Hello, world!')),
        Route::get('/hello-again', fn() => new Response('Hello again, world!')),
    ]),
    Route::get('/bye', fn() => new Response('Goodbye, world!')),
]);
```

### HTTP methods

[](#http-methods)

You can specify the HTTP method that the route should match:

```
Route::get('/hello', MyHandler::class);
Route::post('/hello', MyHandler::class);
Route::put('/hello', MyHandler::class);
Route::patch('/hello', MyHandler::class);
Route::delete('/hello', MyHandler::class);
Route::options('/hello', MyHandler::class);
```

If you want a route to match multiple HTTP methods, you can use the `some` method:

```
Route::some(['GET', 'POST'], '/hello', MyHandler::class);
```

You can also use the `any` method to match all HTTP methods:

```
Route::any('/hello', MyHandler::class);
```

### Path parameters

[](#path-parameters)

#### Basic usage

[](#basic-usage)

You can define route parameters by using the `{}` syntax in the route path.

```
Route::get('/hello/{name}', MyHandler::class);
```

The matched parameters will be available in the request attributes.

```
class MyHandler implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        $name = $request->getAttribute('name');
        return new Response("Hello, $name!");
    }
}

Route::get('/hello/{name}', MyHandler::class);
```

#### Custom parameter patterns

[](#custom-parameter-patterns)

By default, the parameters are matched by the `[^/]+` regex (any characters that are not a `/`).

You can specify a custom pattern by using the `where` parameter:

```
// This route will only match if the name contains only letters
Route::get('/hello/{name}', MyHandler::class, where: ['name' => '[a-zA-Z]+']);
```

#### Custom parameter patterns in a group

[](#custom-parameter-patterns-in-a-group)

Parameters patterns can also be defined globally for all routes inside a group:

```
$routes = new RouteGroup(
    [
        Route::get('/hello/{name}', MyHandler::class),
        Route::get('/bye/{name}', MyOtherHandler::class),
    ],
    where: ['name' => '[a-zA-Z]+'],
);
```

### Route handlers

[](#route-handlers)

#### Closures

[](#closures)

The simplest way to define a route handler is to use a closure.
The closure must return a PSR-7 ResponseInterface.

```
Route::get('/hello', fn() => new Response('Hello, world!'));
```

Closures can take in parameters: the request and a request handler (the router itself).

```
Route::get('/hello', function (ServerRequestInterface $request) {
    return new Response('Hello, world!');
});

Route::get('/hello', function (ServerRequestInterface $request, RequestHandlerInterface $router) {
    return new Response('Hello, world!');
});
```

#### RequestHandlerInterface

[](#requesthandlerinterface)

A route handler can be a callable, but it can also be a PSR RequestHandlerInterface.

```
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Server\ServerRequestInterface;
use Psr\Http\Server\ResponseInterface;

class MyHandler implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        return new Response('Hello, world!');
    }
}

Route::get('/hello', new MyHandler());
```

#### MiddlewareInterface

[](#middlewareinterface)

Sometimes, you might want a handler to be able to "refuse" to handle the request, and pass it to the next handler in the chain.

This is done by using a PSR MiddlewareInterface as a route handler :

```
use Psr\Http\Server\MiddlewareInterface;

class MyHandler implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        if (resourceDoesNotExist()) {
            // We don't want this handler to continue processing the request,
            // so we pass the responsability to the next handler
            return $handler->handle($request);
        }

        /* ... */
    }
}

$routes = new RouteGroup([
    // This handler will be called first
    Route::get('/{ressource}', fn() => new MyHandler()),
    // This handler will be called next
    Route::get('/{ressource}', fn() => new Response('Hello, world!')),
]);
```

#### Dependency injection

[](#dependency-injection)

Instead of using a closure or a class instance, your handler can be a class name. The router will then resolve the class using the PSR container you injected into the router.

```
Route::get('/hello', MyHandler::class);
```

*The router will resolve this handler by calling `get(MyHandler::class)` on the container. This means that you can use any value that the container can resolve into a valid route handler.*

### Additional attributes

[](#additional-attributes)

You can add additional attributes to a route by using the `with` method.
Just like path parameters, these attributes will be available in the request attributes.

```
class MyHandler implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        $name = $request->getAttribute('name');
        return new Response("Hello, $name!");
    }
}

// Notice there is no name parameter in the route path
Route::get('/hello', MyHandler::class, with: ['name' => 'world']);
```

Attributes can also be defined globally for all the routes inside a group:

```
$routes = new RouteGroup(
    [
        Route::get('/hello', MyHandler::class),
        Route::get('/bye', MyOtherHandler::class),
    ],
    with: ['name' => 'world'],
);
```

### Middlewares

[](#middlewares)

Middlewares are classes that can modify the request and/or the response before and after the route handler is called.

They can be applied to a route group.

```
$routes = new RouteGroup(
    [
        Route::get('/hello', MyHandler::class),
    ],
    middlewares: [
        MyMiddleware::class,
        MyOtherMiddleware::class,
    ],
);
```

The middleware class must implement the PSR `\Psr\Http\Server\MiddlewareInterface` interface.

### Conditions

[](#conditions)

Conditions are callables that will determine if a route group should be parsed.

```
// This one will be parsed
$routes = new RouteGroup(
    [
        Route::get('/hello', MyHandler::class),
    ],
    conditions: [
        fn(ServerRequestInterface $request) => true,
    ],
);

// This one will NOT be parsed
$routes = new RouteGroup(
    [
        Route::get('/hello', MyHandler::class),
    ],
    conditions: [
        fn(ServerRequestInterface $request) => false,
    ],
);
```

Additionally, conditions can return an array of attributes that will be added to the request attributes.

```
class MyHandler implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        $name = $request->getAttribute('name');
        return new Response("Hello, $name!");
    }
}

$routes = new RouteGroup(
    [
        Route::get('/hello', MyHandler::class),
    ],
    conditions: [
        // This condition will add the 'name' attribute to the request
        fn(ServerRequestInterface $request) => ['name' => 'world'],
    ],
);
```

If a condition returns an array, it is assumed that the route group should be parsed.

If any condition returns `false`, the route group will not be parsed:

```
// This one will NOT be parsed
$routes = new RouteGroup(
    [
        Route::get('/hello', MyHandler::class),
    ],
    conditions: [
        fn(ServerRequestInterface $request) => true,
        fn(ServerRequestInterface $request) => false,
    ],
);
```

### Naming routes

[](#naming-routes)

Routes can be named.

```
Route::get('/hello', MyHandler::class, name: 'hello_route');
```

Using the router, you can then generate the path to a named route:

```
$router->pathTo('hello_route'); // Will return '/hello'
```

If a route has parameters, you can pass them as the second argument:

```
Route::get('/hello/{name}', MyHandler::class, name: 'hello_route');

$router->pathTo('hello_route', ['name' => 'world']); // Will return '/hello/world'
```

### Error handling

[](#error-handling)

This router uses custom exceptions to handle errors.

Here is the inheritance tree of those exceptions:

- `IngeniozIT\Router\RouterException` (interface): the base exception, all other exceptions inherit from this one
    - `IngeniozIT\Router\EmptyRouteStack`: thrown when no route has been matched by the router
    - `IngeniozIT\Router\Route\RouteException`: (interface) the base exception for route errors
        - `IngeniozIT\Router\Route\Exception\InvalidRouteHandler`: thrown when the route handler is not a valid request handler
        - `IngeniozIT\Router\Route\Exception\InvalidRouteResponse`: thrown when the route handler does not return a PSR-7 ResponseInterface
        - `IngeniozIT\Router\Route\Exception\RouteNotFound`: thrown when calling `$router->pathTo` with a route name that does not exist
        - `IngeniozIT\Router\Route\Exception\InvalidRouteParameter`: thrown when calling `$router->pathTo` with invalid parameters
        - `IngeniozIT\Router\Route\Exception\MissingRouteParameters`: thrown when calling `$router->pathTo` with missing parameters
    - `IngeniozIT\Router\Middleware\MiddlewareException`: (interface) the base exception for middleware errors
        - `IngeniozIT\Router\Middleware\Exception\InvalidMiddlewareHandler`: thrown when a middleware is not a valid middleware handler
        - `IngeniozIT\Router\Middleware\Exception\InvalidMiddlewareResponse`: thrown when a middleware does not return a PSR-7 ResponseInterface
    - `IngeniozIT\Router\Condition\ConditionException`: (interface) the base exception for condition errors
        - `IngeniozIT\Router\Condition\Exception\InvalidConditionHandler`: thrown when a condition is not a valid condition handler
        - `IngeniozIT\Router\Condition\Exception\InvalidConditionResponse`: thrown when a condition does not return a valid response

###  Health Score

26

—

LowBetter than 41% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity9

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity59

Maturing project, gaining track record

 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 ~29 days

Recently: every ~37 days

Total

6

Last Release

784d ago

Major Versions

v1.1.0 → v2.0.02024-04-12

### Community

Maintainers

![](https://www.gravatar.com/avatar/167b1b0644440255e02a195b70b29024f6dff62bf886c7450553dc13d354a95b?d=identicon)[Ingenioz IT](/maintainers/Ingenioz%20IT)

---

Top Contributors

[![IngeniozIT](https://avatars.githubusercontent.com/u/41050198?v=4)](https://github.com/IngeniozIT "IngeniozIT (24 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Psalm, Rector

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ingenioz-it-router/health.svg)

```
[![Health](https://phpackages.com/badges/ingenioz-it-router/health.svg)](https://phpackages.com/packages/ingenioz-it-router)
```

###  Alternatives

[cakephp/cakephp

The CakePHP framework

8.9k19.5M1.8k](/packages/cakephp-cakephp)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[cakephp/authentication

Authentication plugin for CakePHP

1214.1M106](/packages/cakephp-authentication)[typo3/cms-core

TYPO3 CMS Core

3713.2M5.1k](/packages/typo3-cms-core)[cakephp/authorization

Authorization abstraction layer plugin for CakePHP

762.5M51](/packages/cakephp-authorization)[windwalker/framework

The next generation PHP framework.

25740.3k1](/packages/windwalker-framework)

PHPackages © 2026

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