PHPackages                             horizom/dispatcher - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. horizom/dispatcher

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

horizom/dispatcher
==================

A PSR-15 middleware dispatcher and pipeline for PHP 8+.

5.2.1(2mo ago)02563MITPHPPHP ^8.0

Since Jul 10Pushed 2mo ago1 watchersCompare

[ Source](https://github.com/horizom/dispatcher)[ Packagist](https://packagist.org/packages/horizom/dispatcher)[ Docs](https://horizom.github.io)[ RSS](/packages/horizom-dispatcher/feed)WikiDiscussions master Synced today

READMEChangelog (6)Dependencies (8)Versions (7)Used By (3)

horizom/dispatcher
==================

[](#horizomdispatcher)

A lightweight, dependency-free PSR-15 middleware dispatcher and pipeline for PHP 8+.

[![Total Downloads](https://camo.githubusercontent.com/b9814bd018e3440a6797bed49c4243a22b019679433bbed9d26a7d6ca5fcc222/68747470733a2f2f706f7365722e707567782e6f72672f686f72697a6f6d2f646973706174636865722f642f746f74616c2e737667)](https://packagist.org/packages/horizom/dispatcher)[![Latest Stable Version](https://camo.githubusercontent.com/8bd215a88d8b7d16ed52a44b16edb8fd4dcd7214f8b216d075b1405cb75950e1/68747470733a2f2f706f7365722e707567782e6f72672f686f72697a6f6d2f646973706174636865722f762f737461626c652e737667)](https://packagist.org/packages/horizom/dispatcher)[![License](https://camo.githubusercontent.com/e55cd25fd5dcc662e9c31380f7a5346222a80184a8134895f559dde6a2e3be69/68747470733a2f2f706f7365722e707567782e6f72672f686f72697a6f6d2f64746f2f6c6963656e73652e737667)](https://packagist.org/packages/horizom/dto)

---

Features
--------

[](#features)

- **Two dispatch strategies** — flat-list `Dispatcher` and linked-list `MiddlewarePipe`
- **PSR-15 compliant** — works with any `MiddlewareInterface` / `RequestHandlerInterface` implementation
- **Optional PSR-11 container** — resolve middleware from any container by class name
- **Fluent API** — chain `add()` calls on `Dispatcher`
- **Deep-clone safe** — `MiddlewarePipe` cloning does not share mutable state
- **Zero production dependencies** beyond the PSR packages

---

Requirements
------------

[](#requirements)

RequirementVersionPHP^8.0psr/http-message^1.0 | ^2.0psr/http-server-handler^1.0psr/http-server-middleware^1.0psr/container *(optional)*^1.0 | ^2.0---

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

[](#installation)

```
composer require horizom/dispatcher
```

---

Concepts
--------

[](#concepts)

### `Dispatcher` — flat-list strategy

[](#dispatcher--flat-list-strategy)

The `Dispatcher` keeps an ordered array of middlewares and an integer step counter. Calling `dispatch()` always resets the counter to `0` and calls `handle()` on the first entry. Each middleware is expected to call `$handler->handle($request)` to advance to the next step.

```
[MiddlewareA] → [MiddlewareB] → [TerminalHandler]
     ↓                ↓                ↓
  process()        process()        handle()

```

### `MiddlewarePipe` — linked-list strategy

[](#middlewarepipe--linked-list-strategy)

The `MiddlewarePipeFactory` builds an immutable linked list of `MiddlewarePipe` nodes. Each node holds a reference to the current entry and the next node. The chain is terminated by an `EmptyRequestHandler` sentinel that throws if reached.

```
MiddlewarePipe(A) → MiddlewarePipe(B) → MiddlewarePipe(Handler) → EmptyRequestHandler

```

---

Usage
-----

[](#usage)

### Flat-list Dispatcher

[](#flat-list-dispatcher)

```
use Horizom\Dispatcher\Dispatcher;

$dispatcher = new Dispatcher([
    new AuthMiddleware(),
    new LoggingMiddleware(),
    new FinalHandler(),   // implements RequestHandlerInterface
]);

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

Add middleware after construction with the fluent `add()` API:

```
$dispatcher = (new Dispatcher())
    ->add(new AuthMiddleware())
    ->add(new LoggingMiddleware())
    ->add(new FinalHandler());

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

Use `dispatch()` multiple times safely — the internal pointer is reset on every call:

```
$responseA = $dispatcher->dispatch($requestA);
$responseB = $dispatcher->dispatch($requestB); // works correctly
```

Invoke as a callable (e.g. in a routing layer):

```
$response = $dispatcher($request);
```

### Linked-list MiddlewarePipe

[](#linked-list-middlewarepipe)

```
use Horizom\Dispatcher\MiddlewarePipeFactory;

$factory = new MiddlewarePipeFactory();

$pipe = $factory->create([
    new AuthMiddleware(),
    new LoggingMiddleware(),
    new FinalHandler(),
]);

$response = $pipe->handle($request);
```

#### Merging pipelines

[](#merging-pipelines)

Sub-pipelines (instances of `MiddlewarePipe`) can be composed into a larger pipeline:

```
$authPipe = $factory->create([new AuthMiddleware(), new RateLimitMiddleware()]);

$pipe = $factory->create([
    $authPipe,               // merged in-place
    new LoggingMiddleware(),
    new FinalHandler(),
]);
```

> **Note:** A sub-pipeline that contains an intermediate terminal `RequestHandlerInterface`(i.e. a handler that is not the `EmptyRequestHandler` sentinel) cannot be merged and will throw an `InvalidArgumentException`.

### DispatcherFactory / MiddlewarePipeFactory

[](#dispatcherfactory--middlewarepipefactory)

Both factories accept an optional `MiddlewareResolverInterface` argument:

```
use Horizom\Dispatcher\DispatcherFactory;
use Horizom\Dispatcher\MiddlewarePipeFactory;

$factory    = new DispatcherFactory($customResolver);
$dispatcher = $factory->create([AuthMiddleware::class, LoggingMiddleware::class]);

$pipeFactory = new MiddlewarePipeFactory($customResolver);
$pipe        = $pipeFactory->create([AuthMiddleware::class, FinalHandler::class]);
```

### Resolving middleware from a PSR-11 container

[](#resolving-middleware-from-a-psr-11-container)

Pass any PSR-11 `ContainerInterface` to `MiddlewareResolver` to enable string-based resolution:

```
use Horizom\Dispatcher\Dispatcher;
use Horizom\Dispatcher\MiddlewareResolver;

$resolver   = new MiddlewareResolver($container);
$dispatcher = new Dispatcher(
    [AuthMiddleware::class, LoggingMiddleware::class, FinalHandler::class],
    $resolver
);

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

The container must return an instance of `MiddlewareInterface` or `RequestHandlerInterface`, otherwise a `TypeError` is thrown with a descriptive message.

---

API Reference
-------------

[](#api-reference)

### `Dispatcher`

[](#dispatcher)

MethodDescription`__construct(array $middlewares = [], ?MiddlewareResolverInterface $resolver = null)`Build the dispatcher, optionally pre-loading middlewares.`add(MiddlewareInterface|RequestHandlerInterface|string $middleware): self`Append a middleware (fluent).`handle(ServerRequestInterface $request): ResponseInterface`Advance one step in the stack (PSR-15).`dispatch(ServerRequestInterface $request): ResponseInterface`Reset the pointer and run the full stack.`__invoke(ServerRequestInterface $request): ResponseInterface`Alias for `dispatch()`.### `MiddlewarePipe`

[](#middlewarepipe)

MethodDescription`__construct(MiddlewareInterface|RequestHandlerInterface $handler, RequestHandlerInterface $next)`Create a pipeline node.`handle(ServerRequestInterface $request): ResponseInterface`Execute this node and forward to `$next` if needed.`getHandler(): MiddlewareInterface|RequestHandlerInterface`Return the current handler.`getNext(): RequestHandlerInterface`Return the next node.`setNext(RequestHandlerInterface $next): void`Replace the next node.### `MiddlewareResolver`

[](#middlewareresolver)

MethodDescription`__construct(?ContainerInterface $container = null)`Optionally inject a PSR-11 container.`resolve(MiddlewareInterface|RequestHandlerInterface|string $middleware): MiddlewareInterface|RequestHandlerInterface`Resolve a middleware instance (from container if string).### `EmptyRequestHandler`

[](#emptyrequesthandler)

Sentinel placed at the end of a `MiddlewarePipe`. Always throws `RequestHandlerException`.

---

Error Handling
--------------

[](#error-handling)

ExceptionThrown when`RequestHandlerException``Dispatcher` stack is exhausted / `EmptyRequestHandler` is reached.`TypeError`A string middleware cannot be resolved (no container, or container returns invalid type).`InvalidArgumentException``MiddlewarePipeFactory::create()` receives an empty array, or a pipeline merge is impossible.---

Testing
-------

[](#testing)

```
composer install
vendor/bin/phpunit
```

53 tests, 76 assertions.

---

License
-------

[](#license)

MIT © [Roland Edi](https://github.com/horizom)

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance86

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity57

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

Recently: every ~345 days

Total

6

Last Release

74d ago

Major Versions

3.1.0 → 5.0.02022-07-10

### Community

Maintainers

![](https://www.gravatar.com/avatar/412f8e786e65f078584b4bc2c0b7a44af095ab2c0d19a5fb7985598576ca4fda?d=identicon)[lambirou](/maintainers/lambirou)

---

Top Contributors

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

---

Tags

dispatcherhorizonphppsr-15phpmiddlewarepsr-15dispatcherpipelinehorizom

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/horizom-dispatcher/health.svg)

```
[![Health](https://phpackages.com/badges/horizom-dispatcher/health.svg)](https://phpackages.com/packages/horizom-dispatcher)
```

###  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)[mcp/sdk

Model Context Protocol SDK for Client and Server applications in PHP

1.5k1.5M89](/packages/mcp-sdk)[typo3/cms-core

TYPO3 CMS Core

3713.2M5.1k](/packages/typo3-cms-core)[jaxon-php/jaxon-core

Jaxon is an open source PHP library for easily creating Ajax web applications

74149.4k30](/packages/jaxon-php-jaxon-core)[xima/xima-typo3-frontend-edit

Frontend Edit - This extension provides an edit button for editors within frontend content elements.

1414.3k](/packages/xima-xima-typo3-frontend-edit)

PHPackages © 2026

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