Lines 89.81% 97 / 108
Methods 80.76% 21 / 26
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 __construct 100.00% 7 / 7 100.00% 1 / 1 3
 getRequestTarget 71.42% 5 / 7 0.00% 0 / 1 4.37
 withRequestTarget 0.00% 0 / 3 0.00% 0 / 1 2
 getMethod 100.00% 1 / 1 100.00% 1 / 1 1
 withMethod 100.00% 4 / 4 100.00% 1 / 1 1
 assertValidMethod 100.00% 2 / 2 100.00% 1 / 1 3
 getUri 100.00% 1 / 1 100.00% 1 / 1 1
 withUri 100.00% 9 / 9 100.00% 1 / 1 5
 [Lucent\Http\Message\AbstractMessage] getProtocolVersion 100.00% 1 / 1 100.00% 1 / 1 1
 [Lucent\Http\Message\AbstractMessage] withProtocolVersion 100.00% 3 / 3 100.00% 1 / 1 1
 [Lucent\Http\Message\AbstractMessage] getHeaders 100.00% 1 / 1 100.00% 1 / 1 1
 [Lucent\Http\Message\AbstractMessage] hasHeader 100.00% 1 / 1 100.00% 1 / 1 1
 [Lucent\Http\Message\AbstractMessage] getHeader 100.00% 5 / 5 100.00% 1 / 1 2
 [Lucent\Http\Message\AbstractMessage] getHeaderLine 100.00% 4 / 4 100.00% 1 / 1 2
 [Lucent\Http\Message\AbstractMessage] withHeader 100.00% 10 / 10 100.00% 1 / 1 3
 [Lucent\Http\Message\AbstractMessage] withAddedHeader 100.00% 12 / 12 100.00% 1 / 1 3
 [Lucent\Http\Message\AbstractMessage] withoutHeader 87.50% 7 / 8 0.00% 0 / 1 2.01
 [Lucent\Http\Message\AbstractMessage] getBody 100.00% 3 / 3 100.00% 1 / 1 2
 [Lucent\Http\Message\AbstractMessage] withBody 100.00% 3 / 3 100.00% 1 / 1 1
 [Lucent\Http\Message\AbstractMessage] setBody 100.00% 1 / 1 100.00% 1 / 1 1
 [Lucent\Http\Message\AbstractMessage] setHeaders 100.00% 2 / 2 100.00% 1 / 1 2
 [Lucent\Http\Message\AbstractMessage] withHeaderInternal 100.00% 6 / 6 100.00% 1 / 1 3
 [Lucent\Http\Message\AbstractMessage] normalizeHeaderName 100.00% 1 / 1 100.00% 1 / 1 1
 [Lucent\Http\Message\AbstractMessage] assertHeaderName 50.00% 3 / 6 0.00% 0 / 1 8.12
 [Lucent\Http\Message\AbstractMessage] assertHeaderValue 66.66% 4 / 6 0.00% 0 / 1 5.93
 [Lucent\Http\Message\AbstractMessage] sanitizeHeaderValue 100.00% 1 / 1 100.00% 1 / 1 1
15final class Request extends AbstractMessage implements RequestInterface
16{
17    /** @var string HTTP method */
18    private string $method = 'GET';
19
20    /** @var UriInterface */
21    private UriInterface $uri;
22
23    /** @var string|null Request target override */
24    private ?string $requestTarget = null;
25
26    public function __construct(string $method = 'GET', ?UriInterface $uri = null)
27    {
28        parent::__construct();
29        $this->method = strtoupper($method);
30        $this->uri = $uri ?? Uri::fromString('/');
31
32        // A request built with a URI should carry that URI's host as its
33        // Host header unless one was already provided.
34        $host = $this->uri->getHost();
35        if ($host !== '') {
36            $port = $this->uri->getPort();
37            $this->withHeaderInternal('Host', $port !== null ? $host . ':' . $port : $host);
38        }
39    }
40
41    public function getRequestTarget(): string
42    {
43        if ($this->requestTarget !== null) {
44            return $this->requestTarget;
45        }
46
47        $target = $this->uri->getPath();
48        $query = $this->uri->getQuery();
49        if ($query !== '') {
50            $target .= '?' . $query;
51        }
52
53        return $target ?: '/';
54    }
55
56    /**
57     * @return static
58     */
59    public function withRequestTarget(string $requestTarget): RequestInterface
60    {
61        $new = clone $this;
62        $new->requestTarget = $requestTarget;
63        return $new;
64    }
65
66    public function getMethod(): string
67    {
68        return $this->method;
69    }
70
71    /**
72     * HTTP method names are case-sensitive; The given string is
73     * stored as-provided.
74     *
75     * @throws \InvalidArgumentException for invalid HTTP methods
76     * @return static
77     */
78    public function withMethod(string $method): RequestInterface
79    {
80        self::assertValidMethod($method);
81
82        $new = clone $this;
83        $new->method = $method;
84        return $new;
85    }
86
87    /**
88     * Assert that an HTTP method is a valid token (RFC 7230 Â§3.1.1).
89     *
90     * @throws \InvalidArgumentException
91     */
92    private static function assertValidMethod(string $method): void
93    {
94        if ($method === '' || !preg_match('/^[!#$%&\'*+.^_`|~0-9A-Za-z-]+$/', $method)) {
95            throw new \InvalidArgumentException("Invalid HTTP method: '$method'");
96        }
97    }
98
99    public function getUri(): UriInterface
100    {
101        return $this->uri;
102    }
103
104    /**
105     * @return static
106     */
107    public function withUri(UriInterface $uri, bool $preserveHost = false): RequestInterface
108    {
109        $new = clone $this;
110        $new->uri = $uri;
111
112        // With $preserveHost=true the Host header is only kept when it is
113        // present AND non-empty; a missing or empty Host header is updated
114        // from the new URI.
115        if (! $preserveHost || $this->getHeaderLine('Host') === '') {
116            $host = $uri->getHost();
117            if ($host !== '') {
118                $port = $uri->getPort();
119                $hostValue = $port !== null ? $host . ':' . $port : $host;
120                $new = $new->withHeader('Host', $hostValue);
121            }
122        }
123
124        return $new;
125    }
126}

Inherited from Lucent\Http\Message\AbstractMessage

35    public function getProtocolVersion(): string
36    {
37        return $this->protocolVersion;
38    }
43    public function withProtocolVersion(string $version): MessageInterface
44    {
45        $new = clone $this;
46        $new->protocolVersion = $version;
47        return $new;
48    }
52    public function getHeaders(): array
53    {
54        return $this->headers;
55    }
57    public function hasHeader(string $name): bool
58    {
59        return isset($this->headerNames[strtolower($name)]);
60    }
62    public function getHeader(string $name): array
63    {
64        $lower = strtolower($name);
65        if (!isset($this->headerNames[$lower])) {
66            return [];
67        }
68        $originalName = $this->headerNames[$lower];
69        return $this->headers[$originalName];
70    }
72    public function getHeaderLine(string $name): string
73    {
74        $values = $this->getHeader($name);
75        if (empty($values)) {
76            return '';
77        }
78        return implode(', ', $values);
79    }
84    public function withHeader(string $name, $value): MessageInterface
85    {
86        $this->assertHeaderName($name);
87        $this->assertHeaderValue($value);
88
89        $new = clone $this;
90        $normalized = $this->normalizeHeaderName($name);
91        $lower = strtolower($name);
92
93        // Remove old header if present
94        if (isset($new->headerNames[$lower])) {
95            unset($new->headers[$new->headerNames[$lower]]);
96        }
97
98        $new->headerNames[$lower] = $normalized;
99        $new->headers[$normalized] = is_array($value) ? array_map([$this, 'sanitizeHeaderValue'], $value) : [$this->sanitizeHeaderValue($value)];
100        return $new;
101    }
106    public function withAddedHeader(string $name, $value): MessageInterface
107    {
108        $this->assertHeaderName($name);
109        $this->assertHeaderValue($value);
110
111        $new = clone $this;
112        $normalized = $this->normalizeHeaderName($name);
113        $lower = strtolower($name);
114
115        $sanitized = is_array($value) ? array_map([$this, 'sanitizeHeaderValue'], $value) : [$this->sanitizeHeaderValue($value)];
116
117        if (isset($new->headerNames[$lower])) {
118            $existingName = $new->headerNames[$lower];
119            $new->headers[$existingName] = array_merge($new->headers[$existingName], $sanitized);
120        } else {
121            $new->headerNames[$lower] = $normalized;
122            $new->headers[$normalized] = $sanitized;
123        }
124
125        return $new;
126    }
131    public function withoutHeader(string $name): MessageInterface
132    {
133        $lower = strtolower($name);
134        if (!isset($this->headerNames[$lower])) {
135            return $this;
136        }
137
138        $new = clone $this;
139        $originalName = $new->headerNames[$lower];
140        unset($new->headers[$originalName]);
141        unset($new->headerNames[$lower]);
142        return $new;
143    }
147    public function getBody(): StreamInterface
148    {
149        if ($this->body === null) {
150            $this->body = Stream::fromString('');
151        }
152        return $this->body;
153    }
162    public function withBody(StreamInterface $body): MessageInterface
163    {
164        $new = clone $this;
165        $new->body = $body;
166        return $new;
167    }
174    protected function setBody(StreamInterface $body): void
175    {
176        $this->body = $body;
177    }
182    protected function setHeaders(array $headers): void
183    {
184        foreach ($headers as $name => $value) {
185            $this->withHeaderInternal($name, $value);
186        }
187    }
192    protected function withHeaderInternal(string $name, $value): void
193    {
194        $normalized = $this->normalizeHeaderName($name);
195        $lower = strtolower($name);
196
197        if (isset($this->headerNames[$lower])) {
198            unset($this->headers[$this->headerNames[$lower]]);
199        }
200
201        $this->headerNames[$lower] = $normalized;
202        $this->headers[$normalized] = is_array($value) ? $value : [$value];
203    }
214    private function normalizeHeaderName(string $name): string
215    {
216        return str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
217    }
225    private function assertHeaderName(string $name): void
226    {
227        if ($name === '' || $name === null) {
228            throw new \InvalidArgumentException('Header name must not be empty');
229        }
230
231        if (preg_match('/[\r\n]/', $name)) {
232            throw new \InvalidArgumentException('Header name must not contain CR or LF characters');
233        }
234
235        if (!preg_match('/^[a-zA-Z0-9!#$%&\'*+\-.\^_`|~]+$/', $name)) {
236            throw new \InvalidArgumentException("Invalid header name: '$name'");
237        }
238    }
246    private function assertHeaderValue(string|array $value): void
247    {
248        $values = is_array($value) ? $value : [$value];
249        foreach ($values as $v) {
250            if (!is_string($v)) {
251                throw new \InvalidArgumentException('Header value must be a string or array of strings');
252            }
253            if (preg_match('/[\r\n]/', $v)) {
254                throw new \InvalidArgumentException('Header value must not contain CR or LF characters');
255            }
256        }
257    }
265    private function sanitizeHeaderValue(string $value): string
266    {
267        return str_replace(["\r", "\n"], '', $value);
268    }