Passed
Push — master ( 73ebf0...7f192b )
by Mihail
14:15
created

HeaderTrait.php (1 issue)

Labels
Severity
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
    public function getHeader($name): array
35
    {
36
        if (false === isset($this->headersMap[$name = \strtolower($name)])) {
37 72
            return [];
38
        }
39 72
        $value = $this->headers[$this->headersMap[$name]];
40 26
        if (\is_string($value)) {
41
            return empty($value) ? [] : [$value];
42
        }
43 51
        $header = [];
44 51
        foreach ($value as $_ => $v) {
45
            $header[] = \join(',', (array)$v);
46
        }
47 3
        return $header;
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): static
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
        return $instance;
64 25
    }
65
66
    public function withHeaders(array $headers): static
67 14
    {
68
        $instance = clone $this;
69 14
        foreach ($headers as $name => $value) {
70
            $instance->normalizeHeader($name, $value, false);
71 14
        }
72 14
        return $instance;
73
    }
74
75 14
    public function withoutHeader($name): static
76
    {
77
        $instance = clone $this;
78 3
        $name     = \strtolower($name);
79
        if (isset($instance->headersMap[$name])) {
80 3
            unset(
81 3
                $instance->headers[$this->headersMap[$name]],
82 3
                $instance->headersMap[$name]
83
            );
84 3
        }
85
        return $instance;
86
    }
87 22
88
    public function withAddedHeader($name, $value): static
89 22
    {
90 22
        $instance = clone $this;
91 18
        $name     = $instance->normalizeHeaderName($name);
92
        $value    = $instance->normalizeHeaderValue($name, $value);
93 15
        if (isset($instance->headersMap[$header = \strtolower($name)])) {
94 11
            $header                     = $instance->headersMap[$header];
95 11
            $instance->headers[$header] = \array_unique(
96
                \array_merge_recursive($instance->headers[$header], $value)
97 15
            );
98 15
        } else {
99
            $instance->headersMap[$header] = $name;
100
            $instance->headers[$name]      = $value;
101 15
        }
102
        return $instance;
103
    }
104 12
105
    public function hasHeader($name): bool
106 12
    {
107
        return \array_key_exists(\strtolower($name), $this->headersMap);
108
    }
109 1
110
    public function replaceHeaders(array $headers): static
111 1
    {
112 1
        $instance          = clone $this;
113
        $instance->headers = $instance->headersMap = [];
114 1
        foreach ($headers as $name => $value) {
115 1
            $instance->normalizeHeader($name, $value, false);
116
        }
117
        return $instance;
118 1
    }
119
120
    /**
121
     * Transforms the nested headers as a flatten array.
122
     * This method is not part of the PSR-7.
123
     *
124
     * @return array
125
     */
126
    public function getFlattenedHeaders(): array
127 28
    {
128
        $flattenHeaders = [];
129 28
        foreach ($this->headers as $name => $value) {
130 28
            $flattenHeaders[] = $name . ':' . \join(',', (array)$value);
131 27
        }
132
        return $flattenHeaders;
133
    }
134 28
135
    public function getCanonicalizedHeaders(array $names = []): string
136
    {
137 4
        if (empty($names)) {
138
            $names = \array_keys($this->headers);
139 4
        }
140 2
        if (!$headers = \array_reduce($names, function($list, $name) {
141
            $name   = \str_replace('_', '-', $name);
142
            $list[] = \strtolower($name) . ':' . \join(',', $this->getHeader($name));
143
            return $list;
144 3
        })) {
145 3
            return '';
146
        }
147 3
        \sort($headers);
148 4
        return \join("\n", $headers);
149 1
    }
150
151
    /**
152 3
     * @param string          $name
153
     * @param string|string[] $value
154 3
     * @param bool            $skipKey
155
     *
156
     * @return void
157
     */
158
    protected function normalizeHeader(string $name, array|string $value, bool $skipKey): void
159
    {
160
        $name = \str_replace(["\r", "\n", "\t"], '', \trim($name));
161
        if (false === $skipKey) {
162
            $name = \ucwords(\str_replace('_', '-', \strtolower($name)), '-');
163
        }
164 63
        $this->headersMap[\strtolower($name)] = $name;
165
166 63
        $this->headers[$name] = $this->normalizeHeaderValue($name, $value);
167
    }
168 63
169 61
    /**
170
     * @param array $headers Associative headers array
171
     *
172 63
     * @return static
173
     */
174 63
    protected function setHeaders(array $headers): static
175 63
    {
176
        try {
177
            foreach ($headers as $name => $value) {
178
                $this->normalizeHeader($name, $value, false);
179
            }
180
            return $this;
181
        } catch (\TypeError $e) {
182 156
            throw new \InvalidArgumentException($e->getMessage(), HttpStatus::BAD_REQUEST, $e);
183
        }
184 156
    }
185 17
186
    /**
187
     * @param string $name
188 156
     *
189
     * @return string Normalized header name
190
     */
191
    protected function normalizeHeaderName(string $name): string
192
    {
193
        $name = \str_replace(["\r", "\n", "\t"], '', \trim($name));
194
        if ('' !== $name) {
195
            return $name;
196 53
        }
197
        throw new \InvalidArgumentException('Empty header name', HttpStatus::BAD_REQUEST);
198
    }
199 53
200 6
    /**
201 6
     * @param string          $name
202 6
     * @param string|string[] $value
203
     *
204
     * @return array
205
     */
206 47
    protected function normalizeHeaderValue(string $name, $value): array
207 2
    {
208
        if (false === \is_array($value)) {
209
            $value = (array)$value;
210 45
        }
211
        try {
212
            if (empty($value = \array_map(function($v): string {
213
                return \trim(\preg_replace('/\s+/', ' ', $v));
214
            }, $value))) {
0 ignored issues
show
It seems like $value can also be of type string; however, parameter $array of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

214
            }, /** @scrutinizer ignore-type */ $value))) {
Loading history...
215
                throw new \InvalidArgumentException(
216
                    \sprintf('The value for header "%s" cannot be empty', $name),
217
                    HttpStatus::BAD_REQUEST);
218
            }
219 94
            return $value;
220
        } catch (\TypeError $e) {
221 94
            throw new \InvalidArgumentException(
222 94
                \sprintf('Invalid value for header "%s", expects a string or array of strings', $name),
223 94
                HttpStatus::BAD_REQUEST, $e);
224 87
        }
225 85
    }
226
}
227