Completed
Push — master ( dd6356...9040ae )
by Jacob
9s
created

RestRequest::getQueryString()   B

Complexity

Conditions 7
Paths 48

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 21
rs 7.551
cc 7
eloc 14
nc 48
nop 0
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
    /**
163
     * Gets the scheme, such as http or https.
164
     *
165
     * @return  string
166
     */
167
    public function getScheme()
168
    {
169
        return $this->parsedUri['scheme'];
170
    }
171
172
    /**
173
     * Gets the hostname.
174
     *
175
     * @return  string
176
     */
177
    public function getHost()
178
    {
179
        return $this->parsedUri['host'];
180
    }
181
182
    /**
183
     * Gets the request method, such as GET, POST, PATCH, etc.
184
     *
185
     * @return  string
186
     */
187
    public function getMethod()
188
    {
189
        return $this->requestMethod;
190
    }
191
192
    /**
193
     * Gets the requested entity type.
194
     *
195
     * @return  string
196
     */
197
    public function getEntityType()
198
    {
199
        return $this->entityType;
200
    }
201
202
    /**
203
     * Gets the requested entity identifier (id), if sent.
204
     *
205
     * @return  string|null
206
     */
207
    public function getIdentifier()
208
    {
209
        return $this->identifier;
210
    }
211
212
    /**
213
     * Gets the query string based on the current object properties.
214
     *
215
     * @return  string
216
     */
217
    public function getQueryString()
218
    {
219
        $query = [];
220
        if (!empty($this->pagination)) {
221
            $query[self::PARAM_PAGINATION] = $this->pagination;
222
        }
223
        if (!empty($this->filters)) {
224
            $query[self::PARAM_FILTERING] = $this->filters;
225
        }
226
        foreach ($this->fields as $modelType => $fields) {
227
            $query[self::PARAM_FIELDSETS][$modelType] = implode(',', $fields);
228
        }
229
        $sort = [];
230
        foreach ($this->sorting as $key => $direction) {
231
            $sort[] = (1 === $direction) ? $key : sprintf('-%s', $key);
232
        }
233
        if (!empty($sort)) {
234
            $query[self::PARAM_SORTING] = implode(',', $sort);
235
        }
236
        return http_build_query($query);
237
    }
238
239
    /**
240
     * Determines if an entity identifier (id) was sent with the request.
241
     *
242
     * @return  bool
243
     */
244
    public function hasIdentifier()
245
    {
246
        return null !== $this->getIdentifier();
247
    }
248
249
    /**
250
     * Determines if this is an entity relationship request.
251
     *
252
     * @return  bool
253
     */
254
    public function isRelationship()
255
    {
256
        return !empty($this->relationship);
257
    }
258
259
    /**
260
     * Gets the entity relationship request.
261
     *
262
     * @return  array
263
     */
264
    public function getRelationship()
265
    {
266
        return $this->relationship;
267
    }
268
269
    /**
270
     * Gets the entity relationship field key.
271
     *
272
     * @return  string|null
273
     */
274
    public function getRelationshipFieldKey()
275
    {
276
        if (false === $this->isRelationship()) {
277
            return null;
278
        }
279
        return $this->getRelationship()['field'];
280
    }
281
282
    /**
283
     * Determines if this is an entity relationship retrieve request.
284
     *
285
     * @return  bool
286
     */
287 View Code Duplication
    public function isRelationshipRetrieve()
0 ignored issues
show
Duplication introduced by
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.

Loading history...
288
    {
289
        if (false === $this->isRelationship()) {
290
            return false;
291
        }
292
        return 'self' === $this->getRelationship()['type'];
293
    }
294
295
    /**
296
     * Determines if this is an entity relationship modify (create/update/delete) request.
297
     *
298
     * @return  bool
299
     */
300 View Code Duplication
    public function isRelationshipModify()
0 ignored issues
show
Duplication introduced by
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.

Loading history...
301
    {
302
        if (false === $this->isRelationship()) {
303
            return false;
304
        }
305
        return 'related' === $this->getRelationship()['type'];
306
    }
307
308
    /**
309
     * Determines if this has an autocomplete filter enabled.
310
     *
311
     * @return  bool
312
     */
313 View Code Duplication
    public function isAutocomplete()
0 ignored issues
show
Duplication introduced by
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.

Loading history...
314
    {
315
        if (false === $this->hasFilter(self::FILTER_AUTOCOMPLETE)) {
316
            return false;
317
        }
318
        $autocomplete = $this->getFilter(self::FILTER_AUTOCOMPLETE);
319
        return isset($autocomplete[self::FILTER_AUTOCOMPLETE_KEY]) && isset($autocomplete[self::FILTER_AUTOCOMPLETE_VALUE]);
320
    }
321
322
    /**
323
     * Gets the autocomplete attribute key.
324
     *
325
     * @return  string|null
326
     */
327 View Code Duplication
    public function getAutocompleteKey()
0 ignored issues
show
Duplication introduced by
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.

Loading history...
328
    {
329
        if (false === $this->isAutocomplete()) {
330
            return null;
331
        }
332
        return $this->getFilter(self::FILTER_AUTOCOMPLETE)[self::FILTER_AUTOCOMPLETE_KEY];
333
    }
334
335
    /**
336
     * Gets the autocomplete search value.
337
     *
338
     * @return  string|null
339
     */
340 View Code Duplication
    public function getAutocompleteValue()
0 ignored issues
show
Duplication introduced by
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.

Loading history...
341
    {
342
        if (false === $this->isAutocomplete()) {
343
            return null;
344
        }
345
        return $this->getFilter(self::FILTER_AUTOCOMPLETE)[self::FILTER_AUTOCOMPLETE_VALUE];
346
    }
347
348
    /**
349
     * Determines if this has the database query filter enabled.
350
     *
351
     * @return  bool
352
     */
353 View Code Duplication
    public function isQuery()
0 ignored issues
show
Duplication introduced by
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.

Loading history...
354
    {
355
        if (false === $this->hasFilter(self::FILTER_QUERY)) {
356
            return false;
357
        }
358
        $query = $this->getFilter(self::FILTER_QUERY);
359
        return isset($query[self::FILTER_QUERY_CRITERIA]);
360
    }
361
362
    /**
363
     * Gets the query criteria value.
364
     *
365
     * @return  array
366
     */
367
    public function getQueryCriteria()
368
    {
369
        if (false === $this->isQuery()) {
370
            return [];
371
        }
372
373
        $queryKey = self::FILTER_QUERY;
374
        $criteriaKey = self::FILTER_QUERY_CRITERIA;
375
376
        $decoded = @json_decode($this->getFilter($queryKey)[$criteriaKey], true);
377
        if (!is_array($decoded)) {
378
            $param = sprintf('%s[%s][%s]', self::PARAM_FILTERING, $queryKey, $criteriaKey);
379
            throw RestException::invalidQueryParam($param, 'Was the value sent as valid JSON?');
380
        }
381
        return $decoded;
382
    }
383
384
    /**
385
     * Determines if specific sideloaded include fields were requested.
386
     *
387
     * @return  bool
388
     */
389
    public function hasInclusions()
390
    {
391
        $value = $this->getInclusions();
392
        return !empty($value);
393
    }
394
395
    /**
396
     * Gets specific sideloaded relationship fields to include.
397
     *
398
     * @return  array
399
     */
400
    public function getInclusions()
401
    {
402
        return $this->inclusions;
403
    }
404
405
    /**
406
     * Determines if a specific return fieldset has been specified.
407
     *
408
     * @return  bool
409
     */
410
    public function hasFieldset()
411
    {
412
        $value = $this->getFieldset();
413
        return !empty($value);
414
    }
415
416
    /**
417
     * Gets the return fieldset to use.
418
     *
419
     * @return  array
420
     */
421
    public function getFieldset()
422
    {
423
        return $this->fields;
424
    }
425
426
    /**
427
     * Determines if the request has specified sorting criteria.
428
     *
429
     * @return  bool
430
     */
431
    public function hasSorting()
432
    {
433
        $value = $this->getSorting();
434
        return !empty($value);
435
    }
436
437
    /**
438
     * Gets the sorting criteria.
439
     *
440
     * @return  array
441
     */
442
    public function getSorting()
443
    {
444
        return $this->sorting;
445
    }
446
447
    /**
448
     * Determines if the request has specified pagination (limit/offset) criteria.
449
     *
450
     * @return  bool
451
     */
452
    public function hasPagination()
453
    {
454
        $value = $this->getPagination();
455
        return !empty($value);
456
    }
457
458
    /**
459
     * Gets the pagination (limit/offset) criteria.
460
     *
461
     * @return  array
462
     */
463
    public function getPagination()
464
    {
465
        return $this->pagination;
466
    }
467
468
    /**
469
     * Sets the pagination (limit/offset) criteria.
470
     *
471
     * @param   int     $offset
472
     * @param   int     $limit
473
     * @return  self
474
     */
475
    public function setPagination($offset, $limit)
476
    {
477
        $this->pagination['offset'] = (Integer) $offset;
478
        $this->pagination['limit'] = (Integer) $limit;
479
        return $this;
480
    }
481
482
    /**
483
     * Determines if the request has any filtering criteria.
484
     *
485
     * @return  bool
486
     */
487
    public function hasFilters()
488
    {
489
        return !empty($this->filters);
490
    }
491
492
    /**
493
     * Determines if a specific filter exists, by key
494
     *
495
     * @param   string  $key
496
     * @return  bool
497
     */
498
    public function hasFilter($key)
499
    {
500
        return null !== $this->getFilter($key);
501
    }
502
503
    /**
504
     * Gets a specific filter, by key.
505
     *
506
     * @param   string  $key
507
     * @return  mixed|null
508
     */
509
    public function getFilter($key)
510
    {
511
        if (!isset($this->filters[$key])) {
512
            return null;
513
        }
514
        return $this->filters[$key];
515
    }
516
517
    /**
518
     * Gets the request payload.
519
     *
520
     * @return  RestPayload|null
521
     */
522
    public function getPayload()
523
    {
524
        return $this->payload;
525
    }
526
527
    /**
528
     * Determines if a request payload is present.
529
     *
530
     * @return  bool
531
     */
532
    public function hasPayload()
533
    {
534
        return $this->getPayload() instanceof RestPayload;
535
    }
536
537
    /**
538
     * Parses the incoming request URI/URL and sets the appropriate properties on this RestRequest object.
539
     *
540
     * @param   string  $uri
541
     * @return  self
542
     * @throws  RestException
543
     */
544
    private function parse($uri)
545
    {
546
        $this->parsedUri = parse_url($uri);
0 ignored issues
show
Documentation Bug introduced by
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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

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;
}
Loading history...
547
548
        if (false === strstr($this->parsedUri['path'], $this->config->getRootEndpoint())) {
549
            throw RestException::invalidEndpoint($this->parsedUri['path']);
550
        }
551
552
        $this->parsedUri['path'] = str_replace($this->config->getRootEndpoint(), '', $this->parsedUri['path']);
553
        $this->parsePath($this->parsedUri['path']);
554
555
        $this->parsedUri['query'] = isset($this->parsedUri['query']) ? $this->parsedUri['query'] : '';
556
        $this->parseQueryString($this->parsedUri['query']);
557
558
        return $this;
559
    }
560
561
    /**
562
     * Parses the incoming request path and sets appropriate properties on this RestRequest object.
563
     *
564
     * @param   string  $path
565
     * @return  self
566
     * @throws  RestException
567
     */
568
    private function parsePath($path)
569
    {
570
        $parts = explode('/', trim($path, '/'));
571
        for ($i = 0; $i < 1; $i++) {
572
            // All paths must contain /{workspace_entityType}
573
            if (false === $this->issetNotEmpty($i, $parts)) {
574
                throw RestException::invalidEndpoint($path);
575
            }
576
        }
577
        $this->extractEntityType($parts);
578
        $this->extractIdentifier($parts);
579
        $this->extractRelationship($parts);
580
        return $this;
581
    }
582
583
    /**
584
     * Extracts the entity type from an array of path parts.
585
     *
586
     * @param   array   $parts
587
     * @return  self
588
     */
589
    private function extractEntityType(array $parts)
590
    {
591
        $this->entityType = $parts[0];
592
        return $this;
593
    }
594
595
    /**
596
     * Extracts the entity identifier (id) from an array of path parts.
597
     *
598
     * @param   array   $parts
599
     * @return  self
600
     */
601
    private function extractIdentifier(array $parts)
602
    {
603
        if (isset($parts[1])) {
604
            $this->identifier = $parts[1];
605
        }
606
        return $this;
607
    }
608
609
    /**
610
     * Extracts the entity relationship properties from an array of path parts.
611
     *
612
     * @param   array   $parts
613
     * @return  self
614
     */
615
    private function extractRelationship(array $parts)
616
    {
617
        if (isset($parts[2])) {
618
            if ('relationships' === $parts[2]) {
619
                if (!isset($parts[3])) {
620
                    throw RestException::invalidRelationshipEndpoint($this->parsedUri['path']);
621
                }
622
                $this->relationship = [
623
                    'type'  => 'self',
624
                    'field' => $parts[3],
625
                ];
626
            } else {
627
                $this->relationship = [
628
                    'type'  => 'related',
629
                    'field' => $parts[2],
630
                ];
631
            }
632
        }
633
        return $this;
634
    }
635
636
    /**
637
     * Parses the incoming request query string and sets appropriate properties on this RestRequest object.
638
     *
639
     * @param   string  $queryString
640
     * @return  self
641
     * @throws  RestException
642
     */
643
    private function parseQueryString($queryString)
644
    {
645
        parse_str($queryString, $parsed);
646
647
        $supported = $this->getSupportedParams();
648
        foreach ($parsed as $param => $value) {
0 ignored issues
show
Bug introduced by
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.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
649
            if (!isset($supported[$param])) {
650
                throw RestException::unsupportedQueryParam($param, array_keys($supported));
651
            }
652
        }
653
654
        $this->extractInclusions($parsed);
0 ignored issues
show
Bug introduced by
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.

Loading history...
655
        $this->extractSorting($parsed);
0 ignored issues
show
Bug introduced by
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.

Loading history...
656
        $this->extractFields($parsed);
0 ignored issues
show
Bug introduced by
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.

Loading history...
657
        $this->extractPagination($parsed);
0 ignored issues
show
Bug introduced by
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.

Loading history...
658
        $this->extractFilters($parsed);
0 ignored issues
show
Bug introduced by
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.

Loading history...
659
        return $this;
660
    }
661
662
    /**
663
     * Extracts relationship inclusions from an array of query params.
664
     *
665
     * @param   array   $params
666
     * @return  self
667
     */
668
    private function extractInclusions(array $params)
669
    {
670
        if (false === $this->issetNotEmpty(self::PARAM_INCLUSIONS, $params)) {
671
            if (true === $this->config->includeAllByDefault()) {
672
                $this->inclusions = ['*' => true];
673
            }
674
            return $this;
675
        }
676
        $inclusions = explode(',', $params[self::PARAM_INCLUSIONS]);
677
        foreach ($inclusions as $inclusion) {
678
            if (false !== stristr($inclusion, '.')) {
679
                throw RestException::invalidParamValue(self::PARAM_INCLUSIONS, sprintf('Inclusion via a relationship path, e.g. "%s" is currently not supported.', $inclusion));
680
            }
681
            $this->inclusions[$inclusion] = true;
682
        }
683
        return $this;
684
    }
685
686
    /**
687
     * Extracts sorting criteria from an array of query params.
688
     *
689
     * @param   array   $params
690
     * @return  self
691
     */
692
    private function extractSorting(array $params)
693
    {
694
        if (false === $this->issetNotEmpty(self::PARAM_SORTING, $params)) {
695
            return $this;
696
        }
697
        $sort = explode(',', $params[self::PARAM_SORTING]);
698
        $this->sorting = [];
699
        foreach ($sort as $field) {
700
            $direction = 1;
701
            if (0 === strpos($field, '-')) {
702
                $direction = -1;
703
                $field = str_replace('-', '', $field);
704
            }
705
            $this->sorting[$field] = $direction;
706
        }
707
        return $this;
708
    }
709
710
    /**
711
     * Extracts fields to return from an array of query params.
712
     *
713
     * @param   array   $params
714
     * @return  self
715
     */
716 View Code Duplication
    private function extractFields(array $params)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
717
    {
718
        if (false === $this->issetNotEmpty(self::PARAM_FIELDSETS, $params)) {
719
            return $this;
720
        }
721
        $fields = $params[self::PARAM_FIELDSETS];
722
        if (!is_array($fields)) {
723
            throw RestException::invalidQueryParam(self::PARAM_FIELDSETS, 'The field parameter must be an array of entity type keys to fields.');
724
        }
725
        foreach ($fields as $entityType => $string) {
726
            $this->fields[$entityType] = explode(',', $string);
727
        }
728
        return $this;
729
    }
730
731
    /**
732
     * Extracts pagination criteria from an array of query params.
733
     *
734
     * @param   array   $params
735
     * @return  self
736
     */
737
    private function extractPagination(array $params)
738
    {
739
        if (false === $this->issetNotEmpty(self::PARAM_PAGINATION, $params)) {
740
            return $this;
741
        }
742
        $page = $params[self::PARAM_PAGINATION];
743
        if (!is_array($page) || !isset($page['limit'])) {
744
            throw RestException::invalidQueryParam(self::PARAM_PAGINATION, 'The page parameter must be an array containing at least a limit.');
745
        }
746
        $this->pagination = [
747
            'offset'    => isset($page['offset']) ? (Integer) $page['offset'] : 0,
748
            'limit'     => (Integer) $page['limit'],
749
        ];
750
        return $this;
751
    }
752
753
    /**
754
     * Extracts filtering criteria from an array of query params.
755
     *
756
     * @param   array   $params
757
     * @return  self
758
     */
759 View Code Duplication
    private function extractFilters(array $params)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
760
    {
761
        if (false === $this->issetNotEmpty(self::PARAM_FILTERING, $params)) {
762
            return $this;
763
        }
764
        $filters = $params[self::PARAM_FILTERING];
765
        if (!is_array($filters)) {
766
            throw RestException::invalidQueryParam(self::PARAM_FILTERING, 'The filter parameter must be an array keyed by filter name and value.');
767
        }
768
        foreach ($filters as $key => $value) {
769
            $this->filters[$key] = $value;
770
        }
771
        return $this;
772
    }
773
774
    /**
775
     * Gets query string parameters that this request supports.
776
     *
777
     * @return  array
778
     */
779
    public function getSupportedParams()
780
    {
781
        return [
782
            self::PARAM_INCLUSIONS  => true,
783
            self::PARAM_FIELDSETS   => true,
784
            self::PARAM_SORTING     => true,
785
            self::PARAM_PAGINATION  => true,
786
            self::PARAM_FILTERING   => true,
787
        ];
788
    }
789
790
    /**
791
     * Helper that determines if a key and value is set and is not empty.
792
     *
793
     * @param   string  $key
794
     * @param   mixed   $value
795
     * @return  bool
796
     */
797
    private function issetNotEmpty($key, $value)
798
    {
799
        return isset($value[$key]) && !empty($value[$key]);
800
    }
801
}
802