Skip to content

PHP PSR-17 HTTP Factories — Complete Guide to Request and Response Factories

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about PHP PSR. We cover key concepts, practical examples, and best practices to help you master this topic.

PHP PSR-17 defines Factory interfaces for creating PSR-7 HTTP message objects, enabling Dependency Injection of request, response, stream, URI, and uploaded file instances.

What You'll Learn

By the end of this tutorial, you'll use PSR-17 factories to create requests, responses, streams, URIs, and uploaded files, implement custom factories, and integrate with PSR-11 containers.

Why PSR-17 Matters

PSR-7 objects need constructors with many parameters. PSR-17 factories simplify creation and enable testability by allowing factory injection.

Real-World Use

A controller receives a ResponseFactoryInterface via dependency injection. It creates JSON responses with status codes without depending on a specific PSR-7 implementation.

PSR-17 Path

flowchart LR
  A[PSR-15 Middleware] --> B[PSR-17 HTTP Factory]
  B --> C[PHP-DI Container]
  C --> D[PSR-11 Container]
  D --> E[Middleware Slim]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Response Factory

Create responses with factory instead of constructor.

<?php
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
class JsonController {
    public function __construct(
        private ResponseFactoryInterface $responseFactory
    ) {}
    public function success(mixed $data, int $status = 200): ResponseInterface {
        $response = $this->responseFactory->createResponse($status);
        $body = $response->getBody();
        $body->write(json_encode(["data" => $data]));
        return $response
            ->withHeader("Content-Type", "application/json")
            ->withBody($body);
    }
}

Request Factory

Create server requests from HTTP inputs.

<?php
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;
class RequestBuilder {
    public function __construct(
        private ServerRequestFactoryInterface $requestFactory,
        private UriFactoryInterface $uriFactory,
    ) {}
    public function createFromGlobals(): ServerRequestInterface {
        $uri = $this->uriFactory->createUri($_SERVER["REQUEST_URI"]);
        $request = $this->requestFactory->createServerRequest(
            $_SERVER["REQUEST_METHOD"],
            $uri,
            $_SERVER
        );
        return $request
            ->withParsedBody($_POST)
            ->withQueryParams($_GET)
            ->withCookieParams($_COOKIE)
            ->withUploadedFiles($_FILES);
    }
}

Stream Factory

Create streams from strings, files, or resources.

<?php
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
class FileDownloadController {
    public function __construct(
        private StreamFactoryInterface $streamFactory,
        private ResponseFactoryInterface $responseFactory,
    ) {}
    public function download(string $filePath): ResponseInterface {
        $stream = $this->streamFactory->createStreamFromFile($filePath, "r");
        $response = $this->responseFactory->createResponse(200);
        return $response
            ->withBody($stream)
            ->withHeader("Content-Type", mime_content_type($filePath))
            ->withHeader("Content-Disposition", "attachment; filename=\"" . basename($filePath) . "\"");
    }
}

URI Factory

Create and manipulate URIs using factory.

<?php
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
class LinkBuilder {
    public function __construct(
        private UriFactoryInterface $uriFactory
    ) {}
    public function buildLink(string $path, array $params = []): UriInterface {
        $uri = $this->uriFactory->createUri("https://example.com");
        $uri = $uri->withPath($path);
        if (!empty($params)) {
            $uri = $uri->withQuery(http_build_query($params));
        }
        return $uri;
    }
}

Uploaded File Factory

Create uploaded file instances from PHP $_FILES arrays.

<?php
use Psr\Http\Message\UploadedFileFactoryInterface;
use Psr\Http\Message\UploadedFileInterface;
class FileUploadHandler {
    public function __construct(
        private UploadedFileFactoryInterface $uploadedFileFactory,
        private StreamFactoryInterface $streamFactory,
    ) {}
    public function processUpload(array $fileInfo): UploadedFileInterface {
        $stream = $this->streamFactory->createStreamFromFile($fileInfo["tmp_name"]);
        return $this->uploadedFileFactory->createUploadedFile(
            $stream,
            $fileInfo["size"],
            $fileInfo["error"],
            $fileInfo["name"],
            $fileInfo["type"]
        );
    }
}

Common Mistakes

1. Creating Factory Instances Manually

The point of factories is to inject them. Creating them manually defeats the purpose.

2. Not Injecting Factories in Controllers

Controllers should receive factory interfaces, not concrete implementations, for testability.

3. Forgetting to Set Body on Response

Responses created with createResponse() have an empty body. Always write data to the body.

4. Confusing ServerRequest and Request

ServerRequestFactory creates ServerRequest with server params. RequestFactory creates general Request objects.

5. Not Using Factory for Body Rewriting

When modifying response body, use the stream factory to create new streams rather than modifying directly.

Practice Questions

1. What is the purpose of PSR-17?

Provides factory interfaces for creating PSR-7 HTTP message objects.

2. Which factory would you inject to create response objects?

ResponseFactoryInterface::createResponse(int $code, string $reasonPhrase = '').

3. How do you create a stream from a string?

StreamFactoryInterface::createStream(string $content) wraps a string in a PSR-7 stream.

4. Why should controllers use factories instead of new Response()?

For testability and decoupling. Factories can be mocked or swapped for different PSR-7 implementations.

5. Challenge: Create a PSR-17 factory implementation for Nyholm PSR-7.

<?php
use Nyholm\Psr7\Factory\Psr17Factory;
$factory = new Psr17Factory();
$response = $factory->createResponse(200);
$stream = $factory->createStream("Hello World");
$uri = $factory->createUri("https://example.com/path");
$request = $factory->createServerRequest("GET", $uri);

FAQ

What PSR-17 factories are available?

ResponseFactory, RequestFactory, ServerRequestFactory, StreamFactory, UriFactory, UploadedFileFactory.

Which PSR-7 libraries provide PSR-17 factories?

Guzzle PSR-7, Nyholm PSR-7, Slim PSR-7, and laminas-diactoros all implement PSR-17.

Can I create my own factory implementation?

Yes. Implement the PSR-17 interfaces to wrap your custom PSR-7 implementation.

Are PSR-17 factories registered in the container?

Yes. Register concrete factory implementations in your PSR-11 container and inject the interfaces.

What is createStreamFromFile used for?

Creates a stream from a file path. Useful for file downloads and processing large files.

Mini Project: PSR-17 Based Controller Base Class

Build a base controller using PSR-17 factories.

<?php
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
abstract class BaseController {
    public function __construct(
        protected ResponseFactoryInterface $responseFactory,
        protected StreamFactoryInterface $streamFactory,
    ) {}
    protected function json(mixed $data, int $status = 200): ResponseInterface {
        $response = $this->responseFactory->createResponse($status)
            ->withHeader("Content-Type", "application/json");
        $body = $this->streamFactory->createStream(json_encode($data));
        return $response->withBody($body);
    }
    protected function html(string $content, int $status = 200): ResponseInterface {
        $response = $this->responseFactory->createResponse($status)
            ->withHeader("Content-Type", "text/html");
        $body = $this->streamFactory->createStream($content);
        return $response->withBody($body);
    }
    protected function redirect(string $url, int $status = 302): ResponseInterface {
        return $this->responseFactory->createResponse($status)
            ->withHeader("Location", $url);
    }
}

What's Next

PHP Dependency Injection PHP Middleware Slim PHP PSR-7 HTTP Messages

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro