Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
77 / 77 |
|
100.00% |
11 / 11 |
CRAP | |
100.00% |
1 / 1 |
Request | |
100.00% |
77 / 77 |
|
100.00% |
11 / 11 |
46 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
isHttpsRequest | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isShellRequest | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
parsePath | |
100.00% |
37 / 37 |
|
100.00% |
1 / 1 |
17 | |||
getPath | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
getRequest | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
getServer | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getPost | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
getPayload | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getActual | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
__set_state | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
11 |
1 | <?php |
2 | |
3 | namespace Miniframe\Core; |
4 | |
5 | /** |
6 | * Request object |
7 | * |
8 | * This class represents a HTTP(S) or shell request |
9 | * To get the actual request, use Request::getActual() |
10 | * |
11 | * Shell requests are automatically translated to a path and request data. These requests are the same: |
12 | * - /foo/bar?page=1 |
13 | * - php index.php foo bar --page=1 |
14 | */ |
15 | class Request |
16 | { |
17 | /** |
18 | * A list of all request ($_GET) data |
19 | * |
20 | * @var array<string, string|string[]> |
21 | */ |
22 | private $request_data = array(); |
23 | |
24 | /** |
25 | * A list of all shell arguments (parsed from $_SERVER['argv']) |
26 | * |
27 | * @var array<int|string, mixed> |
28 | */ |
29 | private $shell_arguments = array(); |
30 | |
31 | /** |
32 | * A list of all posted ($_POST) data |
33 | * |
34 | * @var array<string, string|string[]> |
35 | */ |
36 | private $post_data = array(); |
37 | |
38 | /** |
39 | * Payload of the request |
40 | * |
41 | * @var null|string |
42 | */ |
43 | private $payload_data = null; |
44 | |
45 | /** |
46 | * A list of all posted ($_FILES) files |
47 | * |
48 | * @var mixed[] |
49 | * @todo Write a get method for posted files |
50 | */ |
51 | private $files_data = array(); |
52 | |
53 | /** |
54 | * A list of all server ($_SERVER) data |
55 | * |
56 | * @var array<string, string|string[]> |
57 | */ |
58 | private $server_data = array(); |
59 | |
60 | /** |
61 | * Path of the request |
62 | * |
63 | * @var string[] |
64 | */ |
65 | private $path = array(); |
66 | |
67 | /** |
68 | * Creates a new request |
69 | * |
70 | * @param array<string, string|string[]> $server_data A list of all server ($_SERVER) data. |
71 | * @param array<string, string|string[]> $request_data A list of all request ($_GET) data. |
72 | * @param array<string, string|string[]> $post_data A list of all posted ($_POST) data. |
73 | * @param mixed[] $files_data A list of all posted ($_FILES) files. |
74 | * @param string|null $payload_data The full POST payload. |
75 | * |
76 | * @throws \Exception An exception can be thrown when no REQUEST_URI nor argv is populated. |
77 | */ |
78 | public function __construct( |
79 | array $server_data = array(), |
80 | array $request_data = array(), |
81 | array $post_data = array(), |
82 | array $files_data = array(), |
83 | string $payload_data = null |
84 | ) { |
85 | $this->server_data = $server_data; |
86 | $this->request_data = $request_data; |
87 | $this->post_data = $post_data; |
88 | $this->files_data = $files_data; |
89 | $this->payload_data = $payload_data; |
90 | |
91 | // Parses the path |
92 | $this->parsePath(); |
93 | } |
94 | |
95 | /** |
96 | * Returns true when the request is done over HTTPS |
97 | * |
98 | * @return boolean |
99 | */ |
100 | public function isHttpsRequest(): bool |
101 | { |
102 | return isset($this->server_data['HTTPS']); |
103 | } |
104 | |
105 | /** |
106 | * Returns true when the request is done from the shell |
107 | * |
108 | * @return boolean |
109 | */ |
110 | public function isShellRequest(): bool |
111 | { |
112 | return php_sapi_name() == 'cli'; |
113 | } |
114 | |
115 | /** |
116 | * Calculates the path and sets it to the 'path' variable. |
117 | * |
118 | * When on a shell, also populates the request_data variable with shell arguments. |
119 | * |
120 | * @return void |
121 | * @throws \Exception An exception can be thrown when no REQUEST_URI nor argv is populated. |
122 | */ |
123 | private function parsePath(): void |
124 | { |
125 | // Web request |
126 | if ( |
127 | isset($this->server_data['REQUEST_URI']) |
128 | && is_string($this->server_data['REQUEST_URI']) |
129 | && substr($this->server_data['REQUEST_URI'], 0, 1) == '/' |
130 | ) { |
131 | list($request) = explode('?', $this->server_data['REQUEST_URI'], 2); |
132 | if (trim($request, '/') === '') { |
133 | $this->path = array(); |
134 | return; |
135 | } else { |
136 | $this->path = explode('/', trim($request, '/')); |
137 | return; |
138 | } |
139 | } |
140 | |
141 | // Parses shell requests |
142 | if (isset($this->server_data['argv']) && is_array($this->server_data['argv'])) { |
143 | $destination = null; |
144 | $argument_values = $this->server_data['argv']; |
145 | array_shift($argument_values); // Remove the call itself |
146 | foreach ($argument_values as $argument_value) { |
147 | if (substr($argument_value, 0, 2) === '--') { |
148 | // A parameter with two minus signs, remove one so we catch -- and - in the same code |
149 | $argument_value = substr($argument_value, 1); |
150 | } |
151 | if (substr($argument_value, 0, 1) === '-') { |
152 | // A parameter has been specified |
153 | $destination = substr($argument_value, 1); |
154 | if (strpos($destination, '=') !== false) { |
155 | list($destination, $argument_value) = explode("=", $destination, 2); |
156 | } else { |
157 | $argument_value = null; |
158 | } |
159 | } |
160 | |
161 | if ($destination === null) { |
162 | // Before any parameter will be considered as path |
163 | array_push($this->path, $argument_value); |
164 | } elseif (!isset($this->shell_arguments[$destination])) { |
165 | // First time a parameter is mentioned, make sure it's set |
166 | $this->shell_arguments[$destination] = $argument_value ? $argument_value : true; |
167 | } elseif ($argument_value === null) { |
168 | // Parameter is called again, but no new data yet |
169 | continue; |
170 | } elseif ($this->shell_arguments[$destination] === true) { |
171 | // First time text is added for a parameter |
172 | $this->shell_arguments[$destination] = $argument_value; |
173 | } elseif (!is_array($this->shell_arguments[$destination])) { |
174 | // Parameter is mentioned for the second time, convert to array |
175 | $this->shell_arguments[$destination] = array( |
176 | $this->shell_arguments[$destination], |
177 | $argument_value |
178 | ); |
179 | } else { |
180 | // Parameter is mentioned even more, just keep on appending data |
181 | array_push($this->shell_arguments[$destination], $argument_value); |
182 | } |
183 | } |
184 | return; |
185 | } |
186 | |
187 | throw new \RuntimeException('Validate if $_SERVER["REQUEST_URI"] or $_SERVER["argv"] is set properly'); |
188 | } |
189 | |
190 | /** |
191 | * Returns the path of the web request as array when no index is specified, |
192 | * or a specific path item when an index is specified. |
193 | * |
194 | * Returns null when the index is specified but there are not that many path items. |
195 | * Returns a string when the index is specified and there are enough path items. |
196 | * Returns an array if no index is specified. |
197 | * |
198 | * @param integer|null $index The index of the required path. |
199 | * |
200 | * @return ($index is null ? string[] : string|null) |
201 | */ |
202 | public function getPath(int $index = null) |
203 | { |
204 | if ($index === null) { |
205 | return $this->path; |
206 | } |
207 | |
208 | // When the index is negative one, and we're in a shell, return the called command |
209 | if ($index === -1 && $this->isShellRequest()) { |
210 | return $this->server_data['argv'][0] ?? null; |
211 | } |
212 | |
213 | return $this->path[$index] ?? null; |
214 | } |
215 | |
216 | /** |
217 | * Returns all request values when no key is specified, or a specific request value when a key is specified. |
218 | * |
219 | * Returns null when the key is specified but no value is found |
220 | * Returns a string when the key is specified and one value is found |
221 | * Returns an array if no key is specified, or the key has multiple values |
222 | * |
223 | * @param string|null $key The request value to request. |
224 | * |
225 | * @return ($key is null ? array<string, string|string[]> : string|string[]|null) |
226 | */ |
227 | public function getRequest(?string $key = null) |
228 | { |
229 | // Defines the request array |
230 | if ($this->isShellRequest()) { |
231 | $var = 'shell_arguments'; |
232 | } else { |
233 | $var = 'request_data'; |
234 | } |
235 | |
236 | if ($key === null) { |
237 | return $this->{$var}; |
238 | } |
239 | return $this->{$var}[$key] ?? null; |
240 | } |
241 | |
242 | /** |
243 | * Returns all server values when no key is specified, or a specific server value when a key is specified. |
244 | * |
245 | * Returns null when the key is specified but no value is found |
246 | * Returns a string when the key is specified and one value is found |
247 | * Returns an array if no key is specified, or the key has multiple values |
248 | * |
249 | * @param string|null $key The request value to request. |
250 | * |
251 | * @return ($key is null ? array<string, string|string[]> : string|string[]|null) |
252 | */ |
253 | public function getServer(?string $key = null) |
254 | { |
255 | if ($key === null) { |
256 | return $this->server_data; |
257 | } |
258 | |
259 | return $this->server_data[$key] ?? null; |
260 | } |
261 | |
262 | /** |
263 | * Returns all posted values when no key is specified, or a specific posted value when a key is specified. |
264 | * |
265 | * Returns null when the key is specified but no value is found |
266 | * Returns a string when the key is specified and one value is found |
267 | * Returns an array if no key is specified, or the key has multiple values |
268 | * |
269 | * @param string|null $key The request value to request. |
270 | * |
271 | * @return ($key is null ? array<string, string|string[]> : string|string[]|null) |
272 | */ |
273 | public function getPost(?string $key = null) |
274 | { |
275 | if (isset($key)) { |
276 | return isset($this->post_data[$key]) ? $this->post_data[$key] : null; |
277 | } |
278 | return $this->post_data; |
279 | } |
280 | |
281 | /** |
282 | * Returns the posted payload data as string. When no data exists, null will be returned. |
283 | * |
284 | * @return string|null |
285 | */ |
286 | public function getPayload(): ?string |
287 | { |
288 | return $this->payload_data; |
289 | } |
290 | |
291 | /** |
292 | * Returns a Request object based on the actual web request |
293 | * |
294 | * @return Request |
295 | */ |
296 | public static function getActual(): self |
297 | { |
298 | return static::__set_state([ |
299 | 'server_data' => $_SERVER, |
300 | 'request_data' => $_GET, |
301 | 'post_data' => $_POST, |
302 | 'files_data' => $_FILES, |
303 | 'payload_data' => file_get_contents('php://input') ?: null, |
304 | ]); |
305 | } |
306 | |
307 | /** |
308 | * Magic method; sets a request object based on a specific state |
309 | * |
310 | * @param array<string, mixed> $data The actual state. |
311 | * |
312 | * @return Request |
313 | * @throws \Exception An exception can be thrown when no REQUEST_URI nor argv is populated. |
314 | * @see var_export() |
315 | */ |
316 | public static function __set_state(array $data): self |
317 | { |
318 | return new self( |
319 | isset($data['server_data']) && is_array($data['server_data']) ? $data['server_data'] : array(), |
320 | isset($data['request_data']) && is_array($data['request_data']) ? $data['request_data'] : array(), |
321 | isset($data['post_data']) && is_array($data['post_data']) ? $data['post_data'] : array(), |
322 | isset($data['files_data']) && is_array($data['files_data']) ? $data['files_data'] : array(), |
323 | isset($data['payload_data']) && is_string($data['payload_data']) ? $data['payload_data'] : null |
324 | ); |
325 | } |
326 | } |