Skip to content

Repository files navigation

Quillstack Response

Tests Latest Version Downloads PHP Version StyleCI CodeFactor Quality Gate Coverage Maintainability Reliability Security License

The response object based on PSR-7: Response. Full documentation: https://quillstack.org/response

A response is written as a class: what it carries is one method, and the status is where the class says it is. That way an endpoint's answer is a thing with a name rather than an array assembled somewhere in a controller.

Why this exists

A response in an API is nearly always the same shape: a status, a content type, and an object turned into JSON. Every PSR-7 implementation makes you assemble that by hand each time, because they are written for everything HTTP can carry rather than for the one thing an API sends.

So a response here is a class you write once and name — UserResponse, NotFoundResponse — and send() says what it carries. The status code and its reason phrase come from a table checked against RFC 9110, and a status code this library has never heard of is refused rather than answered with an empty phrase, because in an application that is a typo rather than a decision. A response arriving from somewhere else is the other case, and quillstack/http-client says so by overriding it.

Requirements

  • PHP 8.1 or newer

Installation

composer require quillstack/response

Usage

A response of your own

Extend Response and say what it carries:

use Quillstack\Response\Response;

final class UserResponse extends Response
{
    private string $id = '';

    public function setId(string $id): self
    {
        $this->id = $id;

        return $this;
    }

    public function send(): array
    {
        return ['id' => $this->id];
    }
}
$response = (new UserResponse())->setId('42');

$response->getStatusCode();     // 200
$response->getReasonPhrase();   // 'OK'
json_encode($response);         // {"id":"42"}

Saying what happened

The status comes from the constructor, so a response which means something other than success says so where it is defined rather than where it is used:

use Quillstack\HeaderBag\HeaderBag;
use Quillstack\Response\Response;
use Quillstack\Response\StatusCode;

final class NotFoundResponse extends Response
{
    public function __construct(?HeaderBag $headerBag = null)
    {
        parent::__construct(StatusCode::NOT_FOUND, '', $headerBag ?? new HeaderBag());
    }

    public function send(): array
    {
        return ['error' => ['status' => $this->getStatusCode(), 'message' => $this->getReasonPhrase()]];
    }
}

The reason phrase is found from the code, so 404 is Not Found without anybody writing it down twice. Passing one explicitly overrides it.

Headers

Every change hands back a copy, so the response you were given stays as it was:

$response = (new UserResponse())
    ->withHeader('Content-Type', 'application/json')
    ->withAddedHeader('Set-Cookie', 'a=1');

$response->getHeaderLine('content-type');   // 'application/json'

Building one from a factory

$factory->setResponseClass(UserResponse::class);
$response = $factory->createResponse(StatusCode::CREATED);

Technical documentation

AbstractResponse implements Psr\Http\Message\ResponseInterface and JsonSerializable; Response is the class to extend, and send() is the one method to write.

Method Answers
send(): array what this response carries — the one thing you write
getStatusCode(): int / withStatus($code, $reasonPhrase = '') the status
getReasonPhrase(): string found from the code where none was given
getHeaders(), getHeader(), getHeaderLine(), hasHeader() headers, through quillstack/header-bag
withHeader(), withAddedHeader(), withoutHeader() a copy with them changed
getBody() / withBody() the body, as a PSR-7 stream
getProtocolVersion() / withProtocolVersion() the HTTP version

StatusCode names every status this package knows — 44 of them, from CONTINUE (100) to HTTP_VERSION_NOT_SUPPORTED (505) — and StatusCode::REASON_PHRASES maps each to its phrase.

Exception Thrown when
UnknownResponseCodeException the status is not one of them
UnableToFindReasonPhraseException there is no phrase for that code
UnknownResponseClassException the factory is given a class which does not exist

All extend ResponseException.

Benchmark

Measured with quillstack/benchmark on one JSON response — a status, a content type and a twenty-two byte body — built a thousand times. All four produce the same status, phrase, header and body. Runs are interleaved, each figure is the median of five, and PHP is 8.5.7.

Version
quillstack/response v0.8.0
nyholm/psr7 1.8.2
laminas/laminas-diactoros 3.8.0
guzzlehttp/psr7 2.13.0
Per response Relative
quillstack/response 2.86 µs
nyholm/psr7 4.27 µs 1.5×
laminas/laminas-diactoros 7.79 µs 2.7×
guzzlehttp/psr7 8.31 µs 2.9×

Most of that gap is the body: this one keeps a string as a string, where the others write it into a php://temp resource — the same difference measured in quillstack/stream.

What the numbers do not say: all three of the others will carry any body PHP can open — a socket, a compressed resource, a file handle — and construct from any of them. This is built for the response an API sends, which is a status and some JSON.

Tests

composer test
composer test:coverage
composer stan

The rest of Quillstack

This is one component of Quillstack, a PHP framework which is as simple to use as it is strict about what it does.

License

MIT. See LICENSE.

About

The response object based on PSR-7: HTTP messages, and with the main goal: to be simple and fast.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages