Passed
Push — master ( 4ac306...d2aa89 )
by Mihail
05:30
created

HeaderTrait   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 224
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 85
c 3
b 0
f 0
dl 0
loc 224
ccs 94
cts 94
cp 1
rs 9.84
wmc 32

15 Methods

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