PHPackages                             gilsegura/psr-messages-bundle - 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. [API Development](/categories/api)
4. /
5. gilsegura/psr-messages-bundle

ActiveSymfony-bundle[API Development](/categories/api)

gilsegura/psr-messages-bundle
=============================

Symfony integration for gilsegura/psr-messages: registers the parsers, schema resolver, response factories and PSR-15 middlewares as services, wires the Symfony&lt;-&gt;PSR bridge, and adds a #\[Pipeline\] attribute plus an exception subscriber for JSON:API error responses.

1.0.1(today)00MITPHPPHP ^8.4CI passing

Since Jun 18Pushed todayCompare

[ Source](https://github.com/gilsegura/psr-messages-bundle)[ Packagist](https://packagist.org/packages/gilsegura/psr-messages-bundle)[ RSS](/packages/gilsegura-psr-messages-bundle/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (20)Versions (3)Used By (0)

PSR MESSAGES BUNDLE
===================

[](#psr-messages-bundle)

[![tests](https://github.com/gilsegura/psr-messages-bundle/actions/workflows/tests.yaml/badge.svg)](https://github.com/gilsegura/psr-messages-bundle/actions/workflows/tests.yaml)[![codecov](https://camo.githubusercontent.com/549599f1ad3fb8dce6d20175fc9dc109dfafdbcf4c649c1e0a3b42f826dea207/68747470733a2f2f636f6465636f762e696f2f6769746875622f67696c7365677572612f7073722d6d657373616765732d62756e646c652f67726170682f62616467652e737667)](https://codecov.io/github/gilsegura/psr-messages-bundle)[![static analysis](https://github.com/gilsegura/psr-messages-bundle/actions/workflows/static-analysis.yaml/badge.svg)](https://github.com/gilsegura/psr-messages-bundle/actions/workflows/static-analysis.yaml)[![coding standards](https://github.com/gilsegura/psr-messages-bundle/actions/workflows/coding-standards.yaml/badge.svg)](https://github.com/gilsegura/psr-messages-bundle/actions/workflows/coding-standards.yaml)

Symfony bundle that turns a controller into a PSR-15 pipeline. Each controller declares, with a `#[Pipeline]` attribute, the middlewares that should run around it — body parsing, header/query parsing and request/response validation — using the real middlewares from the `gilsegura/psr-messages`, `gilsegura/psr-validator`and `gilsegura/psr-server` packages. The controller is the terminal PSR-15 handler; the pipeline only adds middlewares in front of it.

How it works
------------

[](#how-it-works)

```
#[Pipeline]  →  configurations (plain data; each names its factory)
                     ↓  PipelineResolver  (memoized per controller)
              ($factory)($configuration)   ← factory is an invokable service
                     ↓
              real psr-* middleware  (stateless, reused per worker)
                     ↓
              MiddlewareRunner  [ terminal handler = your controller ]

```

A `kernel.controller` subscriber reads the `#[Pipeline]`, builds the `MiddlewareRunner` once per controller class (memoized for the lifetime of the worker), converts the Symfony request to PSR-7, runs the pipeline and converts the PSR-7 response back. A controller with no `#[Pipeline]` simply runs with no middlewares — it is still the terminal handler.

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

[](#installation)

```
composer require gilsegura/psr-messages-bundle
```

Register the bundle (or let Symfony Flex do it):

```
// config/bundles.php
return [
    Psr\Messages\Bundle\PsrMessagesBundle::class => ['all' => true],
];
```

Writing a controller
--------------------

[](#writing-a-controller)

Extend `AbstractController` and declare the pipeline. Controllers are autoconfigured as Symfony controllers, so you only route to them as usual.

```
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Messages\Bundle\Attribute\Pipeline;
use Psr\Messages\Bundle\Controller\AbstractController;
use Psr\Messages\Bundle\Pipeline\Config\HeadersConfiguration;
use Psr\Messages\Bundle\Pipeline\Config\MediaTypeParser;
use Psr\Messages\Bundle\Pipeline\Config\ParsedBodyConfiguration;
use Psr\Messages\Bundle\Pipeline\Config\Validation\BodyValidationConfiguration;
use Psr\Messages\Bundle\Pipeline\Config\Validation\RequestValidationConfiguration;
use Psr\Messages\Bundle\Pipeline\Config\ValidationConfiguration;

#[Pipeline(
    new HeadersConfiguration(RequestHeaders::class),
    new ValidationConfiguration(
        new RequestValidationConfiguration(new BodyValidationConfiguration('create-user')),
    ),
    new ParsedBodyConfiguration(MediaTypeParser::JSON_API, CreateUserRequest::class),
)]
final readonly class PostUserController extends AbstractController
{
    #[\Override]
    public function __invoke(ServerRequestInterface $request): ResponseInterface
    {
        $body = $request->getParsedBody();   // a CreateUserRequest, already parsed
        // ...
    }
}
```

The order of the configurations in `#[Pipeline]` is the order the middlewares run in.

The two kinds of schema
-----------------------

[](#the-two-kinds-of-schema)

The bundle deliberately resolves schemas in two different ways, because they are two different things:

- **Parsing** (`ParsedBodyConfiguration`, `HeadersConfiguration`, `QueryParamsConfiguration`) references a **`SchemaInterface` class** by its class-string. That class deserializes the raw input into a typed object; it is resolved inline (no container needed).
- **Validation** (`BodyValidationConfiguration`, etc.) references a **JSON Schema file** by an **alias**. The bundle scans the schema directory, registers a `FileFactory` service per `.json`, and the validation factory resolves it by that alias.

So a parser turns the body into a `CreateUserRequest`, while validation checks the raw body against `create-user.json` — independent pieces.

Schemas (validation)
--------------------

[](#schemas-validation)

JSON Schema files live in `config/schemas/` by default. The file name (without `.json`) is the alias you pass to the validation configurations:

```
config/schemas/create-user.json   →  new BodyValidationConfiguration('create-user')

```

Override the directory:

```
// config/packages/psr_messages.php
return static function (Symfony\Config\PsrMessagesConfig $config): void {
    $config->schemaDir('%kernel.project_dir%/config/schemas');
};
```

The directory is registered as a resource, so adding or changing a `.json`invalidates the container in dev.

Mapping exceptions to HTTP status
---------------------------------

[](#mapping-exceptions-to-http-status)

The bundle is exception-agnostic: it ships no status mappers. Provide your own by implementing `ExceptionStatusMapper` — it is autoconfigured by tag and collected by the exception subscriber, which renders a JSON:API error response. The first mapper that returns a `Status` wins; unmapped exceptions fall back to 500.

```
use Psr\Messages\Bundle\Exception\ExceptionStatusMapper;
use Psr\Server\ResponseFactory\Status;

final readonly class ValidationStatusMapper implements ExceptionStatusMapper
{
    #[\Override]
    public function statusFor(\Throwable $throwable): ?Status
    {
        return $throwable instanceof ValidationExceptionInterface
            ? Status::UNPROCESSABLE_CONTENT
            : null;
    }
}
```

Adding your own middleware
--------------------------

[](#adding-your-own-middleware)

Implement a `MiddlewareConfiguration` (plain data that names its factory) and a `MiddlewareFactory` (an invokable that builds the PSR-15 middleware). The factory is autoconfigured by tag, so it is reachable from any `#[Pipeline]` without touching the bundle.

```
use Psr\Http\Server\MiddlewareInterface;
use Psr\Messages\Bundle\Pipeline\MiddlewareConfiguration;
use Psr\Messages\Bundle\Pipeline\MiddlewareFactory;

final readonly class RateLimitConfiguration implements MiddlewareConfiguration
{
    public function __construct(public int $perMinute = 60) {}

    #[\Override]
    public static function factory(): string
    {
        return RateLimitMiddlewareFactory::class;
    }
}

/** @implements MiddlewareFactory */
final readonly class RateLimitMiddlewareFactory implements MiddlewareFactory
{
    #[\Override]
    public function __invoke(MiddlewareConfiguration $configuration): MiddlewareInterface
    {
        return new RateLimitMiddleware($configuration->perMinute);
    }
}
```

Then reference it in a pipeline: `#[Pipeline(new RateLimitConfiguration(120), ...)]`.

Performance
-----------

[](#performance)

The reflection of the `#[Pipeline]` and the `MiddlewareRunner` are built once per controller class and reused for the lifetime of the worker. The first request to a controller pays that one-off cost; every later request is a cached lookup plus the actual middleware work (validation/parsing). Run `make benchmark` to measure it in your environment.

License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

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

Total

2

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/29969702?v=4)[josué](/maintainers/gilsegura)[@gilsegura](https://github.com/gilsegura)

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Rector

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/gilsegura-psr-messages-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/gilsegura-psr-messages-bundle/health.svg)](https://phpackages.com/packages/gilsegura-psr-messages-bundle)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k17.5M373](/packages/easycorp-easyadmin-bundle)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

6941.5M396](/packages/drupal-core-recommended)[drupal/core

Drupal is an open source content management platform powering millions of websites and applications.

19564.8M1.6k](/packages/drupal-core)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.4M196](/packages/sulu-sulu)[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

35729.6k2](/packages/telnyx-telnyx-php)

PHPackages © 2026

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