Completed
Push — v3 ( 4ecdc9...abfb2c )
by Mihail
06:27
created

HeaderTrait.php (1 issue)

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