Passed
Push — develop ( 48a229...de32d6 )
by nguereza
10:40
created

ServerRequest::filterParsedBody()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 7
rs 10
1
<?php
2
3
/**
4
 * Platine HTTP
5
 *
6
 * Platine HTTP Message is the implementation of PSR 7
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine HTTP
11
 * Copyright (c) 2019 Dion Chaika
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
/**
33
 *  @file ServerRequest.php
34
 *
35
 *  The ServerRequest class is the representation of an incoming, server-side HTTP request.
36
 *
37
 *
38
 *  @package    Platine\Http
39
 *  @author Platine Developers Team
40
 *  @copyright  Copyright (c) 2020
41
 *  @license    http://opensource.org/licenses/MIT  MIT License
42
 *  @link   https://www.platine-php.com
43
 *  @version 1.0.0
44
 *  @filesource
45
 */
46
47
declare(strict_types=1);
48
49
namespace Platine\Http;
50
51
use InvalidArgumentException;
52
53
/**
54
 * @class ServerRequest
55
 * @package Platine\Http
56
 */
57
class ServerRequest extends Request implements ServerRequestInterface
58
{
59
    /**
60
     * The array of servers parameters
61
     * @var array<string, mixed>
62
     */
63
    protected array $serverParams = [];
64
65
    /**
66
     * The array of cookie parameters
67
     * @var array<string, mixed>
68
     */
69
    protected array $cookieParams = [];
70
71
    /**
72
     * The array of query parameters
73
     * @var array<string, mixed>
74
     */
75
    protected array $queryParams = [];
76
77
    /**
78
     * The array of uploaded files
79
     * @var array<string|int, mixed|UploadedFileInterface>
80
     */
81
    protected array $uploadedFiles = [];
82
83
    /**
84
     * The parse body content
85
     * @var object|array<string, mixed>|null
86
     */
87
    protected object|array|null $parsedBody;
88
89
    /**
90
     * The array of request attributes
91
     * @var array<string, mixed>
92
     */
93
    protected array $attributes = [];
94
95
    /**
96
     * Create new ServerRequest object
97
     * @param string $method the HTTP request method
98
     * @param UriInterface|string|null $uri
99
     * @param array<string, mixed>  $serverParams the array of server params
100
     */
101
    public function __construct(
102
        string $method = 'GET',
103
        UriInterface|string|null $uri = null,
104
        array $serverParams = []
105
    ) {
106
        $this->serverParams = $serverParams;
107
        parent::__construct($method, $uri);
108
    }
109
110
    /**
111
     * Create instance using global variables
112
     * @return self
113
     * @throws InvalidArgumentException
114
     */
115
    public static function createFromGlobals(): self
116
    {
117
        $method = $_POST['_method'] ?? $_SERVER['REQUEST_METHOD'] ?? 'GET';
118
119
        $protocolVersion = '1.1';
120
        if (!empty($_SERVER['SERVER_PROTOCOL'])) {
121
            $parts = explode('/', $_SERVER['SERVER_PROTOCOL'], 2);
122
            if (!empty($parts[1]) && preg_match('/^\d\.\d$/', $parts[1])) {
123
                $protocolVersion = $parts[1];
124
            }
125
        }
126
127
        $uri = Uri::createFromGlobals();
128
        $uploadedFiles = UploadedFile::createFromGlobals();
129
130
        $request = (new self($method, $uri, $_SERVER))
131
                ->withoutHeader('Host')
132
                ->withProtocolVersion($protocolVersion)
133
                ->withQueryParams($_GET)
134
                ->withParsedBody($_POST)
135
                ->withCookieParams($_COOKIE)
136
                ->withUploadedFiles($uploadedFiles);
137
138
139
        foreach ($_SERVER as $key => $value) {
140
            if (strpos($key, 'HTTP_') === 0) {
141
                $headerName = strtolower(str_replace('_', '-', substr($key, 5)));
142
                $headerNameParts = array_map('ucfirst', explode('-', $headerName));
143
144
                $headerName = implode('-', $headerNameParts);
145
                $headerValues = array_map('trim', explode(',', $value));
146
147
                $request = $request->withAddedHeader($headerName, $headerValues);
148
            }
149
        }
150
151
        if ($protocolVersion === '1.1' && !$request->hasHeader('Host')) {
152
            throw new InvalidArgumentException(
153
                'Invalid request! "HTTP/1.1" request must contain a "Host" header'
154
            );
155
        }
156
157
        return $request->withBody(new Stream('php://input'));
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163
    public function getServerParams(): array
164
    {
165
        return $this->serverParams;
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171
    public function getCookieParams(): array
172
    {
173
        return $this->cookieParams;
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179
    public function withCookieParams(array $cookies): self
180
    {
181
        $that = clone $this;
182
        $that->cookieParams = $cookies;
183
184
        return $that;
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190
    public function getQueryParams(): array
191
    {
192
        return $this->queryParams;
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     */
198
    public function withQueryParams(array $query): self
199
    {
200
        $that = clone $this;
201
        $that->queryParams = $query;
202
203
        return $that;
204
    }
205
206
    /**
207
     * {@inheritdoc}
208
     */
209
    public function getUploadedFiles(): array
210
    {
211
        return $this->uploadedFiles;
212
    }
213
214
    /**
215
     * {@inheritdoc}
216
     */
217
    public function withUploadedFiles(array $uploadedFiles): self
218
    {
219
        $that = clone $this;
220
        $that->uploadedFiles = $this->filterUploadedFiles($uploadedFiles);
221
222
        return $that;
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     */
228
    public function getParsedBody(): array|object|null
229
    {
230
        return $this->parsedBody;
231
    }
232
233
    /**
234
     * {@inheritdoc}
235
     */
236
    public function withParsedBody(array|object|null $data): self
237
    {
238
        $that = clone $this;
239
        $that->parsedBody = $data;
240
241
        return $that;
242
    }
243
244
    /**
245
     * {@inheritdoc}
246
     */
247
    public function getAttributes(): array
248
    {
249
        return $this->attributes;
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255
    public function getAttribute(string $name, mixed $default = null): mixed
256
    {
257
        return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263
    public function withAttribute(string $name, mixed $value): self
264
    {
265
        $that = clone $this;
266
        $that->attributes[$name] = $value;
267
268
        return $that;
269
    }
270
271
    /**
272
     * {@inheritdoc}
273
     */
274
    public function withoutAttribute(string $name): self
275
    {
276
        $that = clone $this;
277
        if (isset($that->attributes[$name])) {
278
            unset($that->attributes[$name]);
279
        }
280
281
        return $that;
282
    }
283
284
    /**
285
     * Filter the uploaded files
286
     * @param  array<int|string, UploadedFileInterface|mixed>  $uploadedFiles the list of uploaded file
287
     * @return array<int|string, UploadedFileInterface>
288
     */
289
    protected function filterUploadedFiles(array $uploadedFiles): array
290
    {
291
        foreach ($uploadedFiles as $uploadedFile) {
292
            if (is_array($uploadedFile)) {
293
                $this->filterUploadedFiles($uploadedFile);
294
            } elseif (!$uploadedFile instanceof UploadedFileInterface) {
295
                throw new InvalidArgumentException(
296
                    'Invalid structure of uploaded files tree, each uploaded '
297
                        . 'file must be an instance of UploadedFileInterface'
298
                );
299
            }
300
        }
301
302
        return $uploadedFiles;
303
    }
304
}
305