Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laminas Diactoros Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

What you're actually getting. Diactoros is not a framework and not a Laravel component — it is a set of concrete classes behind two PSRs: PSR-7 (Request, Response, ServerRequest, Stream, Uri, UploadedFile) and PSR-17 (*Factory interfaces). There is no service provider, no config file, no facade.

The honest day-one question in a Laravel app: Laravel runs on symfony/http-foundation, not PSR-7. Guzzle defaults to guzzlehttp/psr7. So in a typical Laravel project Diactoros is almost never a deliberate choice — it arrives as a transitive dependency, most commonly because laravel/passport needs to bridge Symfony's Request/Response to PSR-7 for league/oauth2-server. Confirm what you have before reading further:

composer why laminas/laminas-diactoros
composer show laminas/laminas-diactoros   # note the version

Install (only if you're deliberately adding it):

composer require laminas/laminas-diactoros

Check the version-to-PHP matrix — the 3.x line has been dropping PHP versions aggressively (3.8.0 added 8.5 and removed 8.1, so ^3.8 implies PHP ≥ 8.2). If you're on an older runtime, pin accordingly.

Where to look first.

  • src/Response/ — JsonResponse, RedirectResponse, HtmlResponse, TextResponse, EmptyResponse. These are the classes you'll actually instantiate by hand.
  • src/ServerRequestFactory.php — fromGlobals() and fromArrays(); the entry point for SAPI-driven apps.
  • src/ResponseFactory.php, StreamFactory.php, UploadedFileFactory.php, UriFactory.php — the PSR-17 set you inject into third-party code.
  • src/SapiEmitter.php — sends a PSR-7 response to the client. Relevant to standalone scripts, almost never to a Laravel controller.
  • Docs: docs.laminas.dev/laminas-diactoros; migration notes in docs/book/migration.md are worth a skim because 3.0 removed PhpInputStream, marshalUriFromSapi(), and the legacy Zend\Diactoros helper functions.

First useful use case. Not "replace Laravel's HTTP layer" — instead: forge a PSR-7 request to test a package that consumes one, or hand a PSR-7 response to a library that demands it. Both are ten-line exercises, not architectural decisions.

Implementation Patterns

1. Let it be the injected PSR-17 implementation

Diactoros' real value in Laravel is as the anonymous factory provider. Register the factories as singletons and let library code ask for interfaces, not classes:

// AppServiceProvider::register()
$this->app->singleton(ResponseFactoryInterface::class, ResponseFactory::class);
$this->app->singleton(StreamFactoryInterface::class, StreamFactory::class);
$this->app->singleton(UriFactoryInterface::class, UriFactory::class);
$this->app->singleton(ServerRequestFactoryInterface::class, ServerRequestFactory::class);
$this->app->singleton(UploadedFileFactoryInterface::class, UploadedFileFactory::class);

The factories are stateless — singletons are safe. Everything downstream (OAuth servers, HAL/JSON-API renderers, PSR-15 stacks) then constructs messages without knowing Diactoros exists.

2. Convert at the boundary, never in the middle

Do not reimplement your controller layer in PSR-7. Convert on ingress and egress only, using symfony/psr-http-message-bridge (already present transitively if you use Passport):

use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;

$psrRequest  = (new PsrHttpFactory($serverRequestFactory, $streamFactory, $uploadedFileFactory, $uriFactory))
    ->createRequest($laravelRequest);

// ... hand $psrRequest to the package ...

$symfonyResponse = (new HttpFoundationFactory())->createResponse($psrResponse);
return $symfonyResponse; // Laravel middleware, cookies, etc. still work

Keep the PSR-7 object's lifetime inside one method or one job. Anything that leaves the boundary should be a Symfony object again.

3. Forge requests and responses in tests

This is the highest-frequency, lowest-risk usage. When unit-testing a service whose signature is handle(ServerRequestInterface $request), build fixtures directly rather than booting the kernel:

$request = (new ServerRequest([], [], 'https://api.example.test/hooks', 'POST'))
    ->withHeader('Content-Type', 'application/json')
    ->withBody((new StreamFactory())->createStream(json_encode(['id' => 42])));

Note that constructing ServerRequest positionally is brittle across majors — prefer ServerRequestFactory::fromArrays() or fromGlobals() with explicit arrays.

4. Emit PSR-7 responses from packages that return them

Third-party code that returns ResponseInterface is common in the OAuth/OIDC and PSR-15 ecosystem. Don't reach for SapiEmitter inside Laravel (it calls header() and echo behind your middleware's back). Convert the response to Symfony, or, for a quick internal endpoint:

return response($psrResponse->getBody()->__toString(), $psrResponse->getStatusCode())
    ->withHeaders(array_map(fn ($v) => implode(', ', $v), $psrResponse->getHeaders()));

withHeaders() doesn't understand multi-value headers; collapse them with implode(', ') as above (or use ->withHeader() per name).

5. Use Diactoros' convenience responses for internal tooling

For artisan commands, webhook receivers, or health endpoints that need to hand a response object to non-Laravel code, the built-ins save boilerplate:

new JsonResponse(['ok' => true], 200, ['X-Trace' => $traceId]);
new RedirectResponse('/login', 302);

JsonResponse sets Content-Type: application/json and defaults to JSON_THROW_ON_ERROR semantics — if you pass your own flags without JSON_THROW_ON_ERROR, that's respected (fixed in 3.1.0, so stay ≥ 3.1).

6. Stream large payloads with php://temp, not php://memory

When a library requires a StreamInterface for a CSV or binary export, the stream factory defaults to php://temp, which spills to disk past ~2 MB. That is the behaviour you want; explicitly requesting php://memory for a 50 MB export will OOM the worker.

Gotchas and Tips

Immutability is the number-one source of bugs. PSR-7 has no mutators. withHeader(), withBody(), withUri(), withStatus() all return new instances. This silently does nothing:

$response->withHeader('X-Request-Id', $id);   // discarded
return $response->withHeader('X-Request-Id', $id);

Streams are one-shot and position-sensitive. After a consumer reads a body to the end, a second getContents() returns ''. __toString() rewinds and reads from the start, which is why it "works" in some places and not others. Rewind defensively before reuse. Related: PhpInputStream was removed in 3.0 — php://input is read-once and non-seekable, so read it once and keep the string.

fromGlobals() and superglobals. Laravel, Octane, queue workers, and PHPUnit all leave $_SERVER/$_FILES in inconsistent states. In 3.0, passing empty arrays to fromGlobals() no longer falls back to superglobals (PR #155) — a behaviour change that breaks code relying on the old fallback. Use fromArrays() with explicit data whenever you're not in a genuine SAPI entry point.

getMetadata() after detach() threw errors until 3.4.0 (issue #22, PR #195). If you touch stream metadata, require ^3.4.

Cookie parsing. Plus signs in cookie values were being converted to spaces until 3.4.0 (issue #165, PR #193). If your cookies legitimately contain + (base64 payloads, signed tokens) and you're on an older 3.x, pin ^3.4.

Extending the classes is the BC-break surface. PSR-7 v2 added void return types on StreamInterface and UploadedFileInterface methods; 3.0.0 matched them. Subclasses that declare no return type (or a mixed one) fatal on upgrade. The same applies to with* overrides. Prefer composition — wrap, don't extend.

Host header vs. Uri host. withHeader('Host', ...) does not change the URI. Use withUri($uri, true) — the second argument preserves the host — or set the authority when constructing the Uri.

moveTo() on UploadedFile is terminal, and getStream() can't be called twice on the same instance. Convert Symfony UploadedFile → Diactoros UploadedFile once, at the boundary, and pass ownership down.

Don't install SapiEmitter into the Laravel request lifecycle. It emits headers directly and will fight your middleware stack, StreamedResponse, and any output buffering. It belongs in public/index.php of a standalone PSR-7 app.

Version pinning is a real decision here. Diactoros moves fast on PHP support and slow on features, and minor releases have carried behaviour fixes (3.1.0 JSON flags, 3.4.0 cookies + stream metadata, 3.6.0/3.7.0 the non-standard 104 reason phrase, 3.9.0 HTTP/3 version support). Lock to a caret range you've tested against and treat upgrades as deliberate.

Debugging tip: when a PSR-7 body appears empty deep in a library, log $stream->isSeekable() and $stream->tell() alongside getMetadata('uri'). Half the "empty response" reports in PSR-7 codebases are a consumed stream, not a missing body.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style