waffle-commons / pipeline
Pipeline component for Waffle framework.
Requires
- php: ^8.5
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- waffle-commons/contracts: 0.1.0-beta2.1
Requires (Dev)
- carthage-software/mago: ^1.29
- cyclonedx/cyclonedx-php-composer: ^6.2
- php-mock/php-mock-phpunit: ^2.15
- phpunit/phpunit: ^12.5
- vimeo/psalm: ^6.16
This package is auto-updated.
Last update: 2026-05-30 19:46:50 UTC
README
Waffle Pipeline Component
Release:
v0.1.0-beta2ย |ยCHANGELOG.mdPSR Compliance: PSR-15 (Psr\Http\Server\MiddlewareInterface,RequestHandlerInterface), PSR-17 (response factory, optional forOPTIONSauto-answer)
The PSR-15 middleware stack that runs every request through the kernel. The stack locks itself the moment a request enters it, so middleware order cannot be tampered with mid-request.
๐ฆ Installation
composer require waffle-commons/pipeline
๐งฑ Surface
| Class | Role |
|---|---|
Waffle\Commons\Pipeline\MiddlewareStack |
final registry of middleware (add, prepend, getMiddlewares, createHandler). Implements MiddlewareStackInterface. |
Waffle\Commons\Pipeline\RequestHandler |
The PSR-15 handler that walks the stack and falls through to a terminal handler. |
Waffle\Commons\Pipeline\CoreRoutingMiddleware |
Routes the request and exposes the resolved controller / route params on the request attributes. Beta-1: raises the contracts-side RouteNotFoundException (instead of a generic RuntimeException) when no route matches, so missing routes render as 404 rather than 500. Accepts an optional PSR-17 ResponseFactoryInterface โ when supplied, an OPTIONS request to a known path is auto-answered 204 + Allow. |
Waffle\Commons\Pipeline\Middleware\TrustedHostMiddleware |
final readonly PSR-15 middleware enforcing the configured trusted-host allowlist (RFC-003 ยง3.2). |
Waffle\Commons\Pipeline\Middleware\SecureHeadersMiddleware |
Adds baseline security response headers (X-Content-Type-Options, etc.). |
๐ Building a stack (Beta-1 canonical order)
use Waffle\Commons\Pipeline\MiddlewareStack; use Waffle\Commons\Pipeline\CoreRoutingMiddleware; use Waffle\Commons\Pipeline\Middleware\SecureHeadersMiddleware; use Waffle\Commons\Pipeline\Middleware\TrustedHostMiddleware; use Waffle\Commons\Security\Middleware\AnonymousSessionMiddleware; use Waffle\Commons\Security\Middleware\CsrfMiddleware; use Waffle\Commons\Security\Middleware\SecurityMiddleware; $stack = new MiddlewareStack(); $stack ->add(new ErrorHandlerMiddleware($renderer, $logger)) // 1. outermost (catches everything) ->add(new TrustedHostMiddleware(['example.com', 'api.example.com'])) ->add(new AnonymousSessionMiddleware()) // 3. issues WAFFLE_SID + _anon_sid attr ->add(new CoreRoutingMiddleware($router, $responseFactory)) // 4. resolves _classname / _method; auto-answers OPTIONS ->add(new CsrfMiddleware($csrfTokenManager)) // 5. validates #[RequiresCsrfToken] using _anon_sid ->add(new SecurityMiddleware($secureContainer, $logger)) // 6. fail-closed ABAC analysis ->add(new SecureHeadersMiddleware()) // 7. innermost โ defensive response headers ; $handler = $stack->createHandler($controllerDispatcher); $response = $handler->handle($serverRequest);
AnonymousSessionMiddleware must run before CsrfMiddleware โ the CSRF HMAC binds to the SID it publishes. CoreRoutingMiddleware must run before both Csrf and Security โ both read _classname/_method from its request attributes.
After createHandler(), the stack is locked. Further add() / prepend() calls raise RuntimeException('MiddlewareStack is locked and cannot be modified during request processing.').
๐ Locking semantics
final class MiddlewareStack implements MiddlewareStackInterface { public private(set) array $middlewares = []; // PHP 8.5 asymmetric visibility public function add(MiddlewareInterface $middleware): static; // fluent public function prepend(MiddlewareInterface $middleware): static; // fluent public function getMiddlewares(): array; public function createHandler(RequestHandlerInterface $fallback): RequestHandlerInterface; }
public private(set) on $middlewares exposes the array for read-only inspection (tests, debug pages) while keeping mutation strictly through add() / prepend().
๐ก๏ธ Trusted-host middleware
TrustedHostMiddleware is final readonly and takes a list of trusted hosts; matching is case-insensitive against UriInterface::getHost(). An empty list disables the check (DEV-only convenience). The middleware throws \InvalidArgumentException on missing or untrusted Host, which ErrorHandlerMiddleware converts to RFC 7807 HTTP 400.
๐ PHP 8.5 features used
- Asymmetric visibility (
public private(set) array $middlewares). final readonlymiddleware classes so mounting them is side-effect-free.#[\Override]on every PSR-15 implementation method.
๐งญ Architectural boundary (mago guard)
An active dependency perimeter is enforced on every CI run by vendor/bin/mago guard (bundled into composer mago; zero baselines). The rules live in mago.toml under [guard.perimeter] โ a forbidden use statement fails the build, not a reviewer.
Production code under Waffle\Commons\Pipeline may depend only on:
Waffle\Commons\Pipeline\**โ itselfWaffle\Commons\Contracts\**โ the shared contracts package, the only Waffle dependency permittedPsr\**โ PSR interfaces (PSR-7 / PSR-15 / PSR-17)@global+Psl\**โ PHP core and the PHP Standard Library
Test code under WaffleTests\Commons\Pipeline is unrestricted (@all). Structural rules are guarded too: interfaces must be named *Interface, Exception\** classes must end in *Exception, and any Enum\** namespace may hold only enum declarations.
Contract-first, component-agnostic by construction: components compose through waffle-commons/contracts, never directly through one another.
๐งช Testing
docker exec -w /waffle-commons/pipeline waffle-dev composer tests
๐ License
MIT โ see LICENSE.md.