HeaderTrait   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 208
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 9
Bugs 1 Features 0
Metric Value
eloc 86
c 9
b 1
f 0
dl 0
loc 208
ccs 83
cts 83
cp 1
rs 9.84
wmc 32

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getHeaders() 0 3 1
A getHeader() 0 14 5
A normalizeHeader() 0 9 2
A getCanonicalizedHeaders() 0 14 3
A getFlattenedHeaders() 0 7 2
A withoutHeader() 0 11 2
A setHeaders() 0 9 3
A withHeader() 0 9 1
A normalizeHeaderValue() 0 14 3
A normalizeHeaderName() 0 7 2
A withAddedHeader() 0 15 2
A withHeaders() 0 7 2
A replaceHeaders() 0 8 2
A hasHeader() 0 3 1
A getHeaderLine() 0 3 1
1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
 *
11
 */
12
13
namespace Koded\Http;
14
15
use Koded\Http\Interfaces\HttpStatus;
16
17
trait HeaderTrait
18
{
19
    /**
20
     * @var array Message headers.
21
     */
22
    protected array $headers = [];
23
24
    /**
25
     * @var array Used for case-insensitivity header name checks
26
     */
27
    protected array $headersMap = [];
28
29
    public function getHeaders(): array
30
    {
31
        return $this->headers;
32 22
    }
33
34 22
    /**
35
     * @param string $name
36
     *
37 72
     * @return array<int, string>
38
     */
39 72
    public function getHeader($name): array
40 26
    {
41
        if (false === isset($this->headersMap[$name = \strtolower($name)])) {
42
            return [];
43 51
        }
44 51
        $value = $this->headers[$this->headersMap[$name]];
45
        if (\is_string($value)) {
46
            return empty($value) ? [] : [$value];
47 3
        }
48
        $header = [];
49
        foreach ($value as $v) {
50 47
            $header[] = \join(',', (array)$v);
51
        }
52 47
        return $header;
53
    }
54
55 32
    public function getHeaderLine($name): string
56
    {
57 32
        return \join(',', $this->getHeader($name));
58 32
    }
59
60 28
    public function withHeader($name, $value): static
61
    {
62 28
        $instance = clone $this;
63
        $name     = $instance->normalizeHeaderName($name);
64 25
65
        $instance->headersMap[\strtolower($name)] = $name;
66
67 14
        $instance->headers[$name] = $this->normalizeHeaderValue($name, $value);
68
        return $instance;
69 14
    }
70
71 14
    public function withHeaders(array $headers): static
72 14
    {
73
        $instance = clone $this;
74
        foreach ($headers as $name => $value) {
75 14
            $instance->normalizeHeader($name, $value, false);
76
        }
77
        return $instance;
78 3
    }
79
80 3
    public function withoutHeader($name): static
81 3
    {
82 3
        $instance = clone $this;
83
        $name     = \strtolower($name);
84 3
        if (isset($instance->headersMap[$name])) {
85
            unset(
86
                $instance->headers[$this->headersMap[$name]],
87 22
                $instance->headersMap[$name]
88
            );
89 22
        }
90 22
        return $instance;
91 18
    }
92
93 15
    public function withAddedHeader($name, $value): static
94 11
    {
95 11
        $instance = clone $this;
96
        $name     = $instance->normalizeHeaderName($name);
97 15
        $value    = $instance->normalizeHeaderValue($name, $value);
98 15
        if (isset($instance->headersMap[$header = \strtolower($name)])) {
99
            $header                     = $instance->headersMap[$header];
100
            $instance->headers[$header] = \array_unique(
101 15
                @\array_merge_recursive($instance->headers[$header], $value)
102
            );
103
        } else {
104 12
            $instance->headersMap[$header] = $name;
105
            $instance->headers[$name]      = $value;
106 12
        }
107
        return $instance;
108
    }
109 1
110
    public function hasHeader($name): bool
111 1
    {
112 1
        return \array_key_exists(\strtolower($name), $this->headersMap);
113
    }
114 1
115 1
    public function replaceHeaders(array $headers): static
116
    {
117
        $instance          = clone $this;
118 1
        $instance->headers = $instance->headersMap = [];
119
        foreach ($headers as $name => $value) {
120
            $instance->normalizeHeader($name, $value, false);
121
        }
122
        return $instance;
123
    }
124
125
    /**
126
     * Transforms the nested headers as a flatten array.
127 28
     * This method is not part of the PSR-7.
128
     *
129 28
     * @return array<int, string>
130 28
     */
131 27
    public function getFlattenedHeaders(): array
132
    {
133
        $flattenHeaders = [];
134 28
        foreach ($this->headers as $name => $value) {
135
            $flattenHeaders[] = $name . ':' . \join(',', (array)$value);
136
        }
137 4
        return $flattenHeaders;
138
    }
139 4
140 2
    public function getCanonicalizedHeaders(array $names = []): string
141
    {
142
        if (empty($names)) {
143
            $names = \array_keys($this->headers);
144 3
        }
145 3
        if (!$headers = \array_reduce($names, function($list, $name) {
146
            $name   = \str_replace('_', '-', $name);
147 3
            $list[] = \strtolower($name) . ':' . \join(',', $this->getHeader($name));
148 4
            return $list;
149 1
        })) {
150
            return '';
151
        }
152 3
        \sort($headers);
153
        return \join("\n", $headers);
154 3
    }
155
156
    /**
157
     * @param string          $name
158
     * @param string|string[] $value
159
     * @param bool            $skipKey
160
     *
161
     * @return void
162
     */
163
    protected function normalizeHeader(string $name, array|string $value, bool $skipKey): void
164 63
    {
165
        $name = \str_replace(["\r", "\n", "\t"], '', \trim($name));
166 63
        if (false === $skipKey) {
167
            $name = \ucwords(\str_replace('_', '-', \strtolower($name)), '-');
168 63
        }
169 61
        $this->headersMap[\strtolower($name)] = $name;
170
171
        $this->headers[$name] = $this->normalizeHeaderValue($name, $value);
172 63
    }
173
174 63
    /**
175 63
     * @param array $headers Associative headers array
176
     *
177
     * @return static
178
     */
179
    protected function setHeaders(array $headers): static
180
    {
181
        try {
182 156
            foreach ($headers as $name => $value) {
183
                $this->normalizeHeader($name, $value, false);
184 156
            }
185 17
            return $this;
186
        } catch (\TypeError $e) {
187
            throw new \InvalidArgumentException($e->getMessage(), HttpStatus::BAD_REQUEST, $e);
188 156
        }
189
    }
190
191
    /**
192
     * @param string $name
193
     *
194
     * @return string Normalized header name
195
     */
196 53
    protected function normalizeHeaderName(string $name): string
197
    {
198
        $name = \str_replace(["\r", "\n", "\t"], '', \trim($name));
199 53
        if ('' !== $name) {
200 6
            return $name;
201 6
        }
202 6
        throw new \InvalidArgumentException('Empty header name', HttpStatus::BAD_REQUEST);
203
    }
204
205
    /**
206 47
     * @param string          $name
207 2
     * @param string|string[] $value
208
     *
209
     * @return array
210 45
     */
211
    protected function normalizeHeaderValue(string $name, mixed $value): array
212
    {
213
        $value = (array)$value;
214
        try {
215
            if (empty($value = \array_map(fn($v): string => \trim(\preg_replace('/\s+/', ' ', $v)), $value))) {
216
                throw new \InvalidArgumentException(
217
                    \sprintf('The value for header "%s" cannot be empty', $name),
218
                    HttpStatus::BAD_REQUEST);
219 94
            }
220
            return $value;
221 94
        } catch (\TypeError $e) {
222 94
            throw new \InvalidArgumentException(
223 94
                \sprintf('Invalid value for header "%s", expects a string or array of strings', $name),
224 87
                HttpStatus::BAD_REQUEST, $e);
225 85
        }
226 85
    }
227
}
228