This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace As3\Modlr\Rest; |
||
4 | |||
5 | /** |
||
6 | * The REST Request object. |
||
7 | * Is created/parsed from a core Request object. |
||
8 | * |
||
9 | * @author Jacob Bare <[email protected]> |
||
10 | */ |
||
11 | class RestRequest |
||
12 | { |
||
13 | /** |
||
14 | * Request parameter (query string) constants. |
||
15 | */ |
||
16 | const PARAM_INCLUSIONS = 'include'; |
||
17 | const PARAM_FIELDSETS = 'fields'; |
||
18 | const PARAM_SORTING = 'sort'; |
||
19 | const PARAM_PAGINATION = 'page'; |
||
20 | const PARAM_FILTERING = 'filter'; |
||
21 | |||
22 | /** |
||
23 | * Filter parameters. |
||
24 | */ |
||
25 | const FILTER_AUTOCOMPLETE = 'autocomplete'; |
||
26 | const FILTER_AUTOCOMPLETE_KEY = 'key'; |
||
27 | const FILTER_AUTOCOMPLETE_VALUE = 'value'; |
||
28 | const FILTER_QUERY = 'query'; |
||
29 | const FILTER_QUERY_CRITERIA = 'criteria'; |
||
30 | |||
31 | /** |
||
32 | * The request method, such as GET, POST, PATCH, etc. |
||
33 | * |
||
34 | * @var string |
||
35 | */ |
||
36 | private $requestMethod; |
||
37 | |||
38 | /** |
||
39 | * The parsed URL/URI, via PHP's parse_url(). |
||
40 | * |
||
41 | * @var array |
||
42 | */ |
||
43 | private $parsedUri = []; |
||
44 | |||
45 | /** |
||
46 | * The entity type requested. |
||
47 | * |
||
48 | * @var string |
||
49 | */ |
||
50 | private $entityType; |
||
51 | |||
52 | /** |
||
53 | * The entity identifier (id) value, if sent. |
||
54 | * |
||
55 | * @var string|null |
||
56 | */ |
||
57 | private $identifier; |
||
58 | |||
59 | /** |
||
60 | * The entity relationship properties, if sent. |
||
61 | * |
||
62 | * @var array |
||
63 | */ |
||
64 | private $relationship = []; |
||
65 | |||
66 | /** |
||
67 | * Relationship fields to include with the response. |
||
68 | * AKA: sideloading the entities of relationships. |
||
69 | * Either a associative array of relationshipKeys => true to specifically include. |
||
70 | * Or a single associative key of '*' => true if all should be included. |
||
71 | * |
||
72 | * @var array |
||
73 | */ |
||
74 | private $inclusions = []; |
||
75 | |||
76 | /** |
||
77 | * Sorting criteria. |
||
78 | * |
||
79 | * @var array |
||
80 | */ |
||
81 | private $sorting = []; |
||
82 | |||
83 | /** |
||
84 | * Fields to only include with the response. |
||
85 | * |
||
86 | * @var array |
||
87 | */ |
||
88 | private $fields = []; |
||
89 | |||
90 | /** |
||
91 | * Pagination (limit/skip) criteria. |
||
92 | * |
||
93 | * @var array |
||
94 | */ |
||
95 | private $pagination = []; |
||
96 | |||
97 | /** |
||
98 | * Any request filters, such as quering, search, autocomplete, etc. |
||
99 | * Must ultimately be handled by the Adapter to function. |
||
100 | * |
||
101 | * @var array |
||
102 | */ |
||
103 | private $filters = []; |
||
104 | |||
105 | /** |
||
106 | * The request payload, if sent. |
||
107 | * Used for updating/creating entities. |
||
108 | * |
||
109 | * @var RestPayload|null |
||
110 | */ |
||
111 | private $payload; |
||
112 | |||
113 | /** |
||
114 | * The REST configuration. |
||
115 | * |
||
116 | * @var RestConfiguration |
||
117 | */ |
||
118 | private $config; |
||
119 | |||
120 | /** |
||
121 | * Constructor. |
||
122 | * |
||
123 | * @param RestConfiguration $config The REST configuration. |
||
124 | * @param string $method The request method. |
||
125 | * @param string $uri The complete URI (URL) of the request, included scheme, host, path, and query string. |
||
126 | * @param string|null $payload The request payload (body). |
||
127 | */ |
||
128 | public function __construct(RestConfiguration $config, $method, $uri, $payload = null) |
||
129 | { |
||
130 | $this->config = $config; |
||
131 | $this->requestMethod = strtoupper($method); |
||
132 | |||
133 | $this->sorting = $config->getDefaultSorting(); |
||
134 | $this->pagination = $config->getDefaultPagination(); |
||
135 | |||
136 | $this->parse($uri); |
||
137 | $this->payload = empty($payload) ? null : new RestPayload($payload); |
||
138 | |||
139 | // Re-configure the config based on the actually request. |
||
140 | $this->config->setHost($this->getHost()); |
||
141 | $this->config->setScheme($this->getScheme()); |
||
142 | } |
||
143 | |||
144 | /** |
||
145 | * Generates the request URL based on its current object state. |
||
146 | * |
||
147 | * @todo Add support for inclusions and other items. |
||
148 | * @return string |
||
149 | */ |
||
150 | public function getUrl() |
||
151 | { |
||
152 | $query = $this->getQueryString(); |
||
153 | return sprintf('%s://%s/%s/%s%s', |
||
154 | $this->getScheme(), |
||
155 | trim($this->getHost(), '/'), |
||
156 | trim($this->config->getRootEndpoint(), '/'), |
||
157 | $this->getEntityType(), |
||
158 | empty($query) ? '' : sprintf('?%s', $query) |
||
159 | ); |
||
160 | } |
||
161 | |||
162 | protected function adjustRootEndpoint($path) |
||
163 | { |
||
164 | $root = $this->config->getRootEndpoint(); |
||
165 | if (0 !== strpos($path, $root)) { |
||
166 | $end = strrpos($path, $root) + strlen($root); |
||
167 | $endpoint = substr($path, 0, $end); |
||
168 | $this->config->setRootEndpoint($endpoint); |
||
169 | } |
||
170 | } |
||
171 | |||
172 | /** |
||
173 | * Gets the scheme, such as http or https. |
||
174 | * |
||
175 | * @return string |
||
176 | */ |
||
177 | public function getScheme() |
||
178 | { |
||
179 | return $this->parsedUri['scheme']; |
||
180 | } |
||
181 | |||
182 | /** |
||
183 | * Gets the hostname. |
||
184 | * |
||
185 | * @return string |
||
186 | */ |
||
187 | public function getHost() |
||
188 | { |
||
189 | return $this->parsedUri['host']; |
||
190 | } |
||
191 | |||
192 | /** |
||
193 | * Gets the request method, such as GET, POST, PATCH, etc. |
||
194 | * |
||
195 | * @return string |
||
196 | */ |
||
197 | public function getMethod() |
||
198 | { |
||
199 | return $this->requestMethod; |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * Gets the requested entity type. |
||
204 | * |
||
205 | * @return string |
||
206 | */ |
||
207 | public function getEntityType() |
||
208 | { |
||
209 | return $this->entityType; |
||
210 | } |
||
211 | |||
212 | /** |
||
213 | * Gets the requested entity identifier (id), if sent. |
||
214 | * |
||
215 | * @return string|null |
||
216 | */ |
||
217 | public function getIdentifier() |
||
218 | { |
||
219 | return $this->identifier; |
||
220 | } |
||
221 | |||
222 | /** |
||
223 | * Gets the query string based on the current object properties. |
||
224 | * |
||
225 | * @return string |
||
226 | */ |
||
227 | public function getQueryString() |
||
228 | { |
||
229 | $query = []; |
||
230 | if (!empty($this->pagination)) { |
||
231 | $query[self::PARAM_PAGINATION] = $this->pagination; |
||
232 | } |
||
233 | if (!empty($this->filters)) { |
||
234 | $query[self::PARAM_FILTERING] = $this->filters; |
||
235 | } |
||
236 | foreach ($this->fields as $modelType => $fields) { |
||
237 | $query[self::PARAM_FIELDSETS][$modelType] = implode(',', $fields); |
||
238 | } |
||
239 | $sort = []; |
||
240 | foreach ($this->sorting as $key => $direction) { |
||
241 | $sort[] = (1 === $direction) ? $key : sprintf('-%s', $key); |
||
242 | } |
||
243 | if (!empty($sort)) { |
||
244 | $query[self::PARAM_SORTING] = implode(',', $sort); |
||
245 | } |
||
246 | return http_build_query($query); |
||
247 | } |
||
248 | |||
249 | /** |
||
250 | * Determines if an entity identifier (id) was sent with the request. |
||
251 | * |
||
252 | * @return bool |
||
253 | */ |
||
254 | public function hasIdentifier() |
||
255 | { |
||
256 | return null !== $this->getIdentifier(); |
||
257 | } |
||
258 | |||
259 | /** |
||
260 | * Determines if this is an entity relationship request. |
||
261 | * |
||
262 | * @return bool |
||
263 | */ |
||
264 | public function isRelationship() |
||
265 | { |
||
266 | return !empty($this->relationship); |
||
267 | } |
||
268 | |||
269 | /** |
||
270 | * Gets the entity relationship request. |
||
271 | * |
||
272 | * @return array |
||
273 | */ |
||
274 | public function getRelationship() |
||
275 | { |
||
276 | return $this->relationship; |
||
277 | } |
||
278 | |||
279 | /** |
||
280 | * Gets the entity relationship field key. |
||
281 | * |
||
282 | * @return string|null |
||
283 | */ |
||
284 | public function getRelationshipFieldKey() |
||
285 | { |
||
286 | if (false === $this->isRelationship()) { |
||
287 | return null; |
||
288 | } |
||
289 | return $this->getRelationship()['field']; |
||
290 | } |
||
291 | |||
292 | /** |
||
293 | * Determines if this is an entity relationship retrieve request. |
||
294 | * |
||
295 | * @return bool |
||
296 | */ |
||
297 | View Code Duplication | public function isRelationshipRetrieve() |
|
0 ignored issues
–
show
|
|||
298 | { |
||
299 | if (false === $this->isRelationship()) { |
||
300 | return false; |
||
301 | } |
||
302 | return 'self' === $this->getRelationship()['type']; |
||
303 | } |
||
304 | |||
305 | /** |
||
306 | * Determines if this is an entity relationship modify (create/update/delete) request. |
||
307 | * |
||
308 | * @return bool |
||
309 | */ |
||
310 | View Code Duplication | public function isRelationshipModify() |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
311 | { |
||
312 | if (false === $this->isRelationship()) { |
||
313 | return false; |
||
314 | } |
||
315 | return 'related' === $this->getRelationship()['type']; |
||
316 | } |
||
317 | |||
318 | /** |
||
319 | * Determines if this has an autocomplete filter enabled. |
||
320 | * |
||
321 | * @return bool |
||
322 | */ |
||
323 | View Code Duplication | public function isAutocomplete() |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
324 | { |
||
325 | if (false === $this->hasFilter(self::FILTER_AUTOCOMPLETE)) { |
||
326 | return false; |
||
327 | } |
||
328 | $autocomplete = $this->getFilter(self::FILTER_AUTOCOMPLETE); |
||
329 | return isset($autocomplete[self::FILTER_AUTOCOMPLETE_KEY]) && isset($autocomplete[self::FILTER_AUTOCOMPLETE_VALUE]); |
||
330 | } |
||
331 | |||
332 | /** |
||
333 | * Gets the autocomplete attribute key. |
||
334 | * |
||
335 | * @return string|null |
||
336 | */ |
||
337 | View Code Duplication | public function getAutocompleteKey() |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
338 | { |
||
339 | if (false === $this->isAutocomplete()) { |
||
340 | return null; |
||
341 | } |
||
342 | return $this->getFilter(self::FILTER_AUTOCOMPLETE)[self::FILTER_AUTOCOMPLETE_KEY]; |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * Gets the autocomplete search value. |
||
347 | * |
||
348 | * @return string|null |
||
349 | */ |
||
350 | View Code Duplication | public function getAutocompleteValue() |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
351 | { |
||
352 | if (false === $this->isAutocomplete()) { |
||
353 | return null; |
||
354 | } |
||
355 | return $this->getFilter(self::FILTER_AUTOCOMPLETE)[self::FILTER_AUTOCOMPLETE_VALUE]; |
||
356 | } |
||
357 | |||
358 | /** |
||
359 | * Determines if this has the database query filter enabled. |
||
360 | * |
||
361 | * @return bool |
||
362 | */ |
||
363 | View Code Duplication | public function isQuery() |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
364 | { |
||
365 | if (false === $this->hasFilter(self::FILTER_QUERY)) { |
||
366 | return false; |
||
367 | } |
||
368 | $query = $this->getFilter(self::FILTER_QUERY); |
||
369 | return isset($query[self::FILTER_QUERY_CRITERIA]); |
||
370 | } |
||
371 | |||
372 | /** |
||
373 | * Gets the query criteria value. |
||
374 | * |
||
375 | * @return array |
||
376 | */ |
||
377 | public function getQueryCriteria() |
||
378 | { |
||
379 | if (false === $this->isQuery()) { |
||
380 | return []; |
||
381 | } |
||
382 | |||
383 | $queryKey = self::FILTER_QUERY; |
||
384 | $criteriaKey = self::FILTER_QUERY_CRITERIA; |
||
385 | |||
386 | $decoded = @json_decode($this->getFilter($queryKey)[$criteriaKey], true); |
||
387 | if (!is_array($decoded)) { |
||
388 | $param = sprintf('%s[%s][%s]', self::PARAM_FILTERING, $queryKey, $criteriaKey); |
||
389 | throw RestException::invalidQueryParam($param, 'Was the value sent as valid JSON?'); |
||
390 | } |
||
391 | return $decoded; |
||
392 | } |
||
393 | |||
394 | /** |
||
395 | * Determines if specific sideloaded include fields were requested. |
||
396 | * |
||
397 | * @return bool |
||
398 | */ |
||
399 | public function hasInclusions() |
||
400 | { |
||
401 | $value = $this->getInclusions(); |
||
402 | return !empty($value); |
||
403 | } |
||
404 | |||
405 | /** |
||
406 | * Gets specific sideloaded relationship fields to include. |
||
407 | * |
||
408 | * @return array |
||
409 | */ |
||
410 | public function getInclusions() |
||
411 | { |
||
412 | return $this->inclusions; |
||
413 | } |
||
414 | |||
415 | /** |
||
416 | * Determines if a specific return fieldset has been specified. |
||
417 | * |
||
418 | * @return bool |
||
419 | */ |
||
420 | public function hasFieldset() |
||
421 | { |
||
422 | $value = $this->getFieldset(); |
||
423 | return !empty($value); |
||
424 | } |
||
425 | |||
426 | /** |
||
427 | * Gets the return fieldset to use. |
||
428 | * |
||
429 | * @return array |
||
430 | */ |
||
431 | public function getFieldset() |
||
432 | { |
||
433 | return $this->fields; |
||
434 | } |
||
435 | |||
436 | /** |
||
437 | * Determines if the request has specified sorting criteria. |
||
438 | * |
||
439 | * @return bool |
||
440 | */ |
||
441 | public function hasSorting() |
||
442 | { |
||
443 | $value = $this->getSorting(); |
||
444 | return !empty($value); |
||
445 | } |
||
446 | |||
447 | /** |
||
448 | * Gets the sorting criteria. |
||
449 | * |
||
450 | * @return array |
||
451 | */ |
||
452 | public function getSorting() |
||
453 | { |
||
454 | return $this->sorting; |
||
455 | } |
||
456 | |||
457 | /** |
||
458 | * Determines if the request has specified pagination (limit/offset) criteria. |
||
459 | * |
||
460 | * @return bool |
||
461 | */ |
||
462 | public function hasPagination() |
||
463 | { |
||
464 | $value = $this->getPagination(); |
||
465 | return !empty($value); |
||
466 | } |
||
467 | |||
468 | /** |
||
469 | * Gets the pagination (limit/offset) criteria. |
||
470 | * |
||
471 | * @return array |
||
472 | */ |
||
473 | public function getPagination() |
||
474 | { |
||
475 | return $this->pagination; |
||
476 | } |
||
477 | |||
478 | /** |
||
479 | * Sets the pagination (limit/offset) criteria. |
||
480 | * |
||
481 | * @param int $offset |
||
482 | * @param int $limit |
||
483 | * @return self |
||
484 | */ |
||
485 | public function setPagination($offset, $limit) |
||
486 | { |
||
487 | $this->pagination['offset'] = (Integer) $offset; |
||
488 | $this->pagination['limit'] = (Integer) $limit; |
||
489 | return $this; |
||
490 | } |
||
491 | |||
492 | /** |
||
493 | * Determines if the request has any filtering criteria. |
||
494 | * |
||
495 | * @return bool |
||
496 | */ |
||
497 | public function hasFilters() |
||
498 | { |
||
499 | return !empty($this->filters); |
||
500 | } |
||
501 | |||
502 | /** |
||
503 | * Determines if a specific filter exists, by key |
||
504 | * |
||
505 | * @param string $key |
||
506 | * @return bool |
||
507 | */ |
||
508 | public function hasFilter($key) |
||
509 | { |
||
510 | return null !== $this->getFilter($key); |
||
511 | } |
||
512 | |||
513 | /** |
||
514 | * Gets a specific filter, by key. |
||
515 | * |
||
516 | * @param string $key |
||
517 | * @return mixed|null |
||
518 | */ |
||
519 | public function getFilter($key) |
||
520 | { |
||
521 | if (!isset($this->filters[$key])) { |
||
522 | return null; |
||
523 | } |
||
524 | return $this->filters[$key]; |
||
525 | } |
||
526 | |||
527 | /** |
||
528 | * Gets the request payload. |
||
529 | * |
||
530 | * @return RestPayload|null |
||
531 | */ |
||
532 | public function getPayload() |
||
533 | { |
||
534 | return $this->payload; |
||
535 | } |
||
536 | |||
537 | /** |
||
538 | * Determines if a request payload is present. |
||
539 | * |
||
540 | * @return bool |
||
541 | */ |
||
542 | public function hasPayload() |
||
543 | { |
||
544 | return $this->getPayload() instanceof RestPayload; |
||
545 | } |
||
546 | |||
547 | /** |
||
548 | * Parses the incoming request URI/URL and sets the appropriate properties on this RestRequest object. |
||
549 | * |
||
550 | * @param string $uri |
||
551 | * @return self |
||
552 | * @throws RestException |
||
553 | */ |
||
554 | private function parse($uri) |
||
555 | { |
||
556 | $this->parsedUri = parse_url($uri); |
||
0 ignored issues
–
show
It seems like
parse_url($uri) can also be of type false . However, the property $parsedUri is declared as type array . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
557 | |||
558 | if (false === strstr($this->parsedUri['path'], $this->config->getRootEndpoint())) { |
||
559 | throw RestException::invalidEndpoint($this->parsedUri['path']); |
||
560 | } |
||
561 | |||
562 | $this->adjustRootEndpoint($this->parsedUri['path']); |
||
563 | |||
564 | $this->parsedUri['path'] = str_replace($this->config->getRootEndpoint(), '', $this->parsedUri['path']); |
||
565 | $this->parsePath($this->parsedUri['path']); |
||
566 | |||
567 | $this->parsedUri['query'] = isset($this->parsedUri['query']) ? $this->parsedUri['query'] : ''; |
||
568 | $this->parseQueryString($this->parsedUri['query']); |
||
569 | |||
570 | return $this; |
||
571 | } |
||
572 | |||
573 | /** |
||
574 | * Parses the incoming request path and sets appropriate properties on this RestRequest object. |
||
575 | * |
||
576 | * @param string $path |
||
577 | * @return self |
||
578 | * @throws RestException |
||
579 | */ |
||
580 | private function parsePath($path) |
||
581 | { |
||
582 | $parts = explode('/', trim($path, '/')); |
||
583 | for ($i = 0; $i < 1; $i++) { |
||
584 | // All paths must contain /{workspace_entityType} |
||
585 | if (false === $this->issetNotEmpty($i, $parts)) { |
||
586 | throw RestException::invalidEndpoint($path); |
||
587 | } |
||
588 | } |
||
589 | $this->extractEntityType($parts); |
||
590 | $this->extractIdentifier($parts); |
||
591 | $this->extractRelationship($parts); |
||
592 | return $this; |
||
593 | } |
||
594 | |||
595 | /** |
||
596 | * Extracts the entity type from an array of path parts. |
||
597 | * |
||
598 | * @param array $parts |
||
599 | * @return self |
||
600 | */ |
||
601 | private function extractEntityType(array $parts) |
||
602 | { |
||
603 | $this->entityType = $parts[0]; |
||
604 | return $this; |
||
605 | } |
||
606 | |||
607 | /** |
||
608 | * Extracts the entity identifier (id) from an array of path parts. |
||
609 | * |
||
610 | * @param array $parts |
||
611 | * @return self |
||
612 | */ |
||
613 | private function extractIdentifier(array $parts) |
||
614 | { |
||
615 | if (isset($parts[1])) { |
||
616 | $this->identifier = $parts[1]; |
||
617 | } |
||
618 | return $this; |
||
619 | } |
||
620 | |||
621 | /** |
||
622 | * Extracts the entity relationship properties from an array of path parts. |
||
623 | * |
||
624 | * @param array $parts |
||
625 | * @return self |
||
626 | */ |
||
627 | private function extractRelationship(array $parts) |
||
628 | { |
||
629 | if (isset($parts[2])) { |
||
630 | if ('relationships' === $parts[2]) { |
||
631 | if (!isset($parts[3])) { |
||
632 | throw RestException::invalidRelationshipEndpoint($this->parsedUri['path']); |
||
633 | } |
||
634 | $this->relationship = [ |
||
635 | 'type' => 'self', |
||
636 | 'field' => $parts[3], |
||
637 | ]; |
||
638 | } else { |
||
639 | $this->relationship = [ |
||
640 | 'type' => 'related', |
||
641 | 'field' => $parts[2], |
||
642 | ]; |
||
643 | } |
||
644 | } |
||
645 | return $this; |
||
646 | } |
||
647 | |||
648 | /** |
||
649 | * Parses the incoming request query string and sets appropriate properties on this RestRequest object. |
||
650 | * |
||
651 | * @param string $queryString |
||
652 | * @return self |
||
653 | * @throws RestException |
||
654 | */ |
||
655 | private function parseQueryString($queryString) |
||
656 | { |
||
657 | parse_str($queryString, $parsed); |
||
658 | |||
659 | $supported = $this->getSupportedParams(); |
||
660 | foreach ($parsed as $param => $value) { |
||
0 ignored issues
–
show
The expression
$parsed of type null|array is not guaranteed to be traversable. How about adding an additional type check?
There are different options of fixing this problem.
![]() |
|||
661 | if (!isset($supported[$param])) { |
||
662 | throw RestException::unsupportedQueryParam($param, array_keys($supported)); |
||
663 | } |
||
664 | } |
||
665 | |||
666 | $this->extractInclusions($parsed); |
||
0 ignored issues
–
show
It seems like
$parsed can also be of type null ; however, As3\Modlr\Rest\RestRequest::extractInclusions() does only seem to accept array , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
667 | $this->extractSorting($parsed); |
||
0 ignored issues
–
show
It seems like
$parsed can also be of type null ; however, As3\Modlr\Rest\RestRequest::extractSorting() does only seem to accept array , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
668 | $this->extractFields($parsed); |
||
0 ignored issues
–
show
It seems like
$parsed can also be of type null ; however, As3\Modlr\Rest\RestRequest::extractFields() does only seem to accept array , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
669 | $this->extractPagination($parsed); |
||
0 ignored issues
–
show
It seems like
$parsed can also be of type null ; however, As3\Modlr\Rest\RestRequest::extractPagination() does only seem to accept array , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
670 | $this->extractFilters($parsed); |
||
0 ignored issues
–
show
It seems like
$parsed can also be of type null ; however, As3\Modlr\Rest\RestRequest::extractFilters() does only seem to accept array , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
671 | return $this; |
||
672 | } |
||
673 | |||
674 | /** |
||
675 | * Extracts relationship inclusions from an array of query params. |
||
676 | * |
||
677 | * @param array $params |
||
678 | * @return self |
||
679 | */ |
||
680 | private function extractInclusions(array $params) |
||
681 | { |
||
682 | if (false === $this->issetNotEmpty(self::PARAM_INCLUSIONS, $params)) { |
||
683 | if (true === $this->config->includeAllByDefault()) { |
||
684 | $this->inclusions = ['*' => true]; |
||
685 | } |
||
686 | return $this; |
||
687 | } |
||
688 | $inclusions = explode(',', $params[self::PARAM_INCLUSIONS]); |
||
689 | foreach ($inclusions as $inclusion) { |
||
690 | if (false !== stristr($inclusion, '.')) { |
||
691 | throw RestException::invalidParamValue(self::PARAM_INCLUSIONS, sprintf('Inclusion via a relationship path, e.g. "%s" is currently not supported.', $inclusion)); |
||
692 | } |
||
693 | $this->inclusions[$inclusion] = true; |
||
694 | } |
||
695 | return $this; |
||
696 | } |
||
697 | |||
698 | /** |
||
699 | * Extracts sorting criteria from an array of query params. |
||
700 | * |
||
701 | * @param array $params |
||
702 | * @return self |
||
703 | */ |
||
704 | private function extractSorting(array $params) |
||
705 | { |
||
706 | if (false === $this->issetNotEmpty(self::PARAM_SORTING, $params)) { |
||
707 | return $this; |
||
708 | } |
||
709 | $sort = explode(',', $params[self::PARAM_SORTING]); |
||
710 | $this->sorting = []; |
||
711 | foreach ($sort as $field) { |
||
712 | $direction = 1; |
||
713 | if (0 === strpos($field, '-')) { |
||
714 | $direction = -1; |
||
715 | $field = str_replace('-', '', $field); |
||
716 | } |
||
717 | $this->sorting[$field] = $direction; |
||
718 | } |
||
719 | return $this; |
||
720 | } |
||
721 | |||
722 | /** |
||
723 | * Extracts fields to return from an array of query params. |
||
724 | * |
||
725 | * @param array $params |
||
726 | * @return self |
||
727 | */ |
||
728 | View Code Duplication | private function extractFields(array $params) |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
729 | { |
||
730 | if (false === $this->issetNotEmpty(self::PARAM_FIELDSETS, $params)) { |
||
731 | return $this; |
||
732 | } |
||
733 | $fields = $params[self::PARAM_FIELDSETS]; |
||
734 | if (!is_array($fields)) { |
||
735 | throw RestException::invalidQueryParam(self::PARAM_FIELDSETS, 'The field parameter must be an array of entity type keys to fields.'); |
||
736 | } |
||
737 | foreach ($fields as $entityType => $string) { |
||
738 | $this->fields[$entityType] = explode(',', $string); |
||
739 | } |
||
740 | return $this; |
||
741 | } |
||
742 | |||
743 | /** |
||
744 | * Extracts pagination criteria from an array of query params. |
||
745 | * |
||
746 | * @param array $params |
||
747 | * @return self |
||
748 | */ |
||
749 | private function extractPagination(array $params) |
||
750 | { |
||
751 | if (false === $this->issetNotEmpty(self::PARAM_PAGINATION, $params)) { |
||
752 | return $this; |
||
753 | } |
||
754 | $page = $params[self::PARAM_PAGINATION]; |
||
755 | if (!is_array($page) || !isset($page['limit'])) { |
||
756 | throw RestException::invalidQueryParam(self::PARAM_PAGINATION, 'The page parameter must be an array containing at least a limit.'); |
||
757 | } |
||
758 | $this->pagination = [ |
||
759 | 'offset' => isset($page['offset']) ? (Integer) $page['offset'] : 0, |
||
760 | 'limit' => (Integer) $page['limit'], |
||
761 | ]; |
||
762 | return $this; |
||
763 | } |
||
764 | |||
765 | /** |
||
766 | * Extracts filtering criteria from an array of query params. |
||
767 | * |
||
768 | * @param array $params |
||
769 | * @return self |
||
770 | */ |
||
771 | View Code Duplication | private function extractFilters(array $params) |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
772 | { |
||
773 | if (false === $this->issetNotEmpty(self::PARAM_FILTERING, $params)) { |
||
774 | return $this; |
||
775 | } |
||
776 | $filters = $params[self::PARAM_FILTERING]; |
||
777 | if (!is_array($filters)) { |
||
778 | throw RestException::invalidQueryParam(self::PARAM_FILTERING, 'The filter parameter must be an array keyed by filter name and value.'); |
||
779 | } |
||
780 | foreach ($filters as $key => $value) { |
||
781 | $this->filters[$key] = $value; |
||
782 | } |
||
783 | return $this; |
||
784 | } |
||
785 | |||
786 | /** |
||
787 | * Gets query string parameters that this request supports. |
||
788 | * |
||
789 | * @return array |
||
790 | */ |
||
791 | public function getSupportedParams() |
||
792 | { |
||
793 | return [ |
||
794 | self::PARAM_INCLUSIONS => true, |
||
795 | self::PARAM_FIELDSETS => true, |
||
796 | self::PARAM_SORTING => true, |
||
797 | self::PARAM_PAGINATION => true, |
||
798 | self::PARAM_FILTERING => true, |
||
799 | ]; |
||
800 | } |
||
801 | |||
802 | /** |
||
803 | * Helper that determines if a key and value is set and is not empty. |
||
804 | * |
||
805 | * @param string $key |
||
806 | * @param mixed $value |
||
807 | * @return bool |
||
808 | */ |
||
809 | private function issetNotEmpty($key, $value) |
||
810 | { |
||
811 | return isset($value[$key]) && !empty($value[$key]); |
||
812 | } |
||
813 | } |
||
814 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.