Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
80.77% covered (warning)
80.77%
21 / 26
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Responder
80.77% covered (warning)
80.77%
21 / 26
80.00% covered (warning)
80.00%
4 / 5
13.02
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 jsonLd
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 empty
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 error
37.50% covered (danger)
37.50%
3 / 8
0.00% covered (danger)
0.00%
0 / 1
7.91
 httpDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace LambdaTwelve\OneRecord\Server\Http;
6
7use DateTimeImmutable;
8use DateTimeInterface;
9use DateTimeZone;
10use LambdaTwelve\OneRecord\Api\Error;
11use LambdaTwelve\OneRecord\Api\ErrorDocument;
12use LambdaTwelve\OneRecord\JsonLd\Json;
13use LambdaTwelve\OneRecord\Vocabulary\Generated\Api;
14use Psr\Http\Message\ResponseFactoryInterface;
15use Psr\Http\Message\ResponseInterface;
16use Psr\Http\Message\StreamFactoryInterface;
17
18/**
19 * Builds responses with the headers the spec puts on every one of them:
20 * Content-Type with the negotiated version, Content-Language, and Type for
21 * documents. HEAD responses keep the headers (including Content-Length) and
22 * drop the body.
23 */
24final class Responder
25{
26    public function __construct(
27        private readonly ResponseFactoryInterface $responses,
28        private readonly StreamFactoryInterface $streams,
29    ) {}
30
31    /**
32     * @param array<string, mixed> $document
33     * @param array<string, string> $headers
34     */
35    public function jsonLd(int $status, array $document, Negotiated $negotiated, ?string $type = null, array $headers = [], bool $head = false): ResponseInterface
36    {
37        $json = Json::encode($document);
38        $response = $this->responses->createResponse($status)
39            ->withHeader('Content-Type', $negotiated->contentType())
40            ->withHeader('Content-Language', $negotiated->language)
41            ->withHeader('Content-Length', (string) \strlen($json));
42        if ($type !== null) {
43            $response = $response->withHeader('Type', $type);
44        }
45        foreach ($headers as $name => $value) {
46            $response = $response->withHeader($name, $value);
47        }
48
49        return $head ? $response : $response->withBody($this->streams->createStream($json));
50    }
51
52    /**
53     * @param array<string, string> $headers
54     */
55    public function empty(int $status, Negotiated $negotiated, array $headers = []): ResponseInterface
56    {
57        $response = $this->responses->createResponse($status)
58            ->withHeader('Content-Type', $negotiated->contentType())
59            ->withHeader('Content-Language', $negotiated->language);
60        foreach ($headers as $name => $value) {
61            $response = $response->withHeader($name, $value);
62        }
63
64        return $response;
65    }
66
67    /**
68     * @param list<Error> $errors
69     * @param array<string, string> $headers
70     */
71    public function error(int $status, array $errors, Negotiated $negotiated, array $headers = [], bool $head = false): ResponseInterface
72    {
73        $error = $errors[0] ?? Error::of('Internal server error', (string) $status);
74        if (\count($errors) > 1) {
75            $details = [];
76            foreach ($errors as $each) {
77                foreach ($each->details as $detail) {
78                    $details[] = $detail;
79                }
80            }
81            $error = new Error($error->title, $details, $error->severity);
82        }
83
84        return $this->jsonLd($status, ErrorDocument::write($error, $negotiated->version, $negotiated->language), $negotiated, Api::Error, $headers, $head);
85    }
86
87    public static function httpDate(DateTimeInterface $date): string
88    {
89        return DateTimeImmutable::createFromInterface($date)->setTimezone(new DateTimeZone('GMT'))->format('D, d M Y H:i:s \G\M\T');
90    }
91}