laminas/laminas-diactoros
Laminas Diactoros is a PHP PSR-7 implementation of HTTP messages, providing request, response, server request, stream, upload, and URI classes plus PSR-17 factories for interoperable web applications and middleware.
Verdict: This is a compliance-grade utility, not a framework component. For a Laravel codebase it is architecturally peripheral: Laravel's HTTP kernel is built on Symfony HttpFoundation (Illuminate\Http\Request/Response wrapping Symfony\Component\HttpFoundation\*), not PSR-7. Diactoros therefore never sits on the default request/response path. It earns its place only when a specific dependency is written against PSR-7/PSR-17.
Architecture fit — conditional.
Request, Response, ServerRequest, Stream, UploadedFile, Uri) and PSR-17 factories (ServerRequestFactory, ResponseFactory, StreamFactory, UploadedFileFactory, UriFactory).Request/Response objects — attempting that is a rewrite, not an integration.Psr\Http\Message\*; (b) reusable PSR-15 middleware; (c) an outbound abstraction where you want PSR-18/PSR-7 instead of Guzzle's own types; (d) PSR-17 factories resolved from the container by interface.Integration feasibility — high, but bridged.
Illuminate\Http\Request ⇄ PSR-7 ServerRequest via symfony/psr-http-message-bridge, and a PSR-7 Response back into a Symfony response. The seam is where the behavioral edge cases live (uploaded files, streamed bodies, header casing, multi-value headers).Technical risk — low for the package, medium for the decision.
migration.md.nyholm/psr7 is the Symfony-recommended lightweight default; guzzlehttp/psr7 dominates outbound-client contexts. Diactoros carries Laminas/Mezzio heritage — solid, but a smaller community center of gravity for a Laravel shop.composer why after install.Key questions before adopting:
nyholm/psr7 and guzzlehttp/psr7 on equal footing (transitive dependency count, PSR-17 factory ergonomics)?Http facade covers the need, PSR-7 may be redundant.Stack fit: Additive only. Install alongside Laravel; nothing in the framework changes. The one meaningful touchpoint is the container — bind the PSR-17 factory interfaces to Diactoros implementations so libraries resolve by interface rather than instantiating concrete types.
Recommended boundary: Treat Diactoros as an adapter layer at the edges — inbound conversion for libraries that demand a ServerRequest, outbound construction for libraries that emit a PSR-7 Response. Never let Laminas\Diactoros\* concrete types leak into controllers, domain code, or job payloads.
Migration path:
composer require laminas/laminas-diactoros, register factory bindings, wire the bridge only on the routes/SDK calls that need it.new GuzzleHttp\Psr7\Response(), instanceof Nyholm\Psr7\...), then swap. Interface-level swaps are safe by construction; concrete-class usage is the only real lock-in, so grep for it before committing.void return types on StreamInterface/UploadedFileInterface, removal of PhpInputStream, marshalUriFromSapi, and the Zend-namespaced helpers). Pure consumers can treat 3.x as a drop-in.Compatibility: PSR-7 v2, PSR-17, PHP 8.2–8.5 on the 3.x line — a clean overlap with Laravel 11/12's PHP 8.2 floor. Watch for constraint conflicts if any legacy package still pins the 2.x line.
Sequencing (roughly two sprints for a scoped use case):
Maintenance: Low. Vendor code — no runtime services, no configuration, no migrations, no schema. It ships on a predictable minor cadence (several per year, mostly PHP-version support, QA tooling, and small fixes) with per-release notes and a maintained migration guide. The Roave BC check gives a credible "patch/minor won't break you" guarantee, which is exactly what you want from a dependency you plan to forget about. Realistic budget: one dependency-bump PR per PHP minor upgrade.
Support: Community-maintained through GitHub issues/PRs and the Laminas docs site; no commercial SLA unless Laminas support is purchased. This is the weakest dimension for a Laravel shop — your team, not the Laminas community, will be first responder for the problems you actually hit, because those problems will live in the Symfony↔PSR-7 seam rather than in Diactoros. A documented security policy exists, which matters for procurement review.
Scaling: Negligible throughput impact if the bridge is confined to specific endpoints. The cost to watch is per-request object churn: PSR-7 messages are immutable, so every header/body mutation clones, and full-body buffering duplicates memory. Applied as global middleware on every request, that is a small but real tax multiplied by RPS. Keep conversions at the edge, stream large bodies instead of materializing them, and never round-trip a request through PSR-7 and back "just in case."
Failure modes to design against:
+ in cookie values). This is a real historical bug class here; 3.4.0 fixed cookie parsing from globals and +→space corruption.getMetadata() after detach raised errors before 3.4.0. Never hand one Diactoros stream to two consumers.$response->withHeader(...) returns a new object; discarding the return value is a silent no-op. This is the most common developer error and it fails quietly.Laminas\Diactoros\Response in a controller signature quietly couples the app to this vendor.^2.x blocks the PHP 8.5 upgrade path.Ramp-up: Small but non-zero. The concepts a Laravel developer must internalize are PSR-7 immutability and stream semantics — both unlike Laravel's mutable, macro-heavy Request/Response. Budget half a day of onboarding plus code-review vigilance for the first few PRs; everything else (install, factories, bridging) is mechanical.
Bottom line for the roadmap: Do not adopt this as infrastructure. Adopt it as a narrowly scoped adapter when — and only when — a named dependency requires PSR-7/PSR-17. When that happens, standardize on one implementation across the codebase, code to interfaces, and confine concrete types to a single namespace. If no such dependency exists today, the correct TPM call is to defer, and to re-run this same evaluation against nyholm/psr7 when the need actually arrives.
How can I help you explore Laravel packages today?