Completed
Push — develop ( 26b032...524257 )
by Neomerx
18:05 queued 09:45
created

QueryParser::parseFilterLink()   C

Complexity

Conditions 11
Paths 9

Size

Total Lines 39
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 11

Importance

Changes 0
Metric Value
dl 0
loc 39
rs 5.2653
c 0
b 0
f 0
ccs 22
cts 22
cp 1
cc 11
eloc 24
nc 9
nop 0
crap 11

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php namespace Limoncello\Flute\Validation\JsonApi;
2
3
/**
4
 * Copyright 2015-2017 [email protected]
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
use Generator;
20
use Limoncello\Contracts\L10n\FormatterFactoryInterface;
21
use Limoncello\Contracts\L10n\FormatterInterface;
22
use Limoncello\Flute\Contracts\Validation\ErrorCodes;
23
use Limoncello\Flute\Contracts\Validation\JsonApiQueryRulesSerializerInterface;
24
use Limoncello\Flute\Contracts\Validation\JsonApiQueryValidatingParserInterface;
25
use Limoncello\Flute\Exceptions\InvalidQueryParametersException;
26
use Limoncello\Flute\Package\FluteSettings;
27
use Limoncello\Flute\Resources\Messages\En\Validation;
28
use Limoncello\Flute\Validation\JsonApi\Execution\JsonApiErrorCollection;
29
use Limoncello\Validation\Contracts\Captures\CaptureAggregatorInterface;
30
use Limoncello\Validation\Contracts\Errors\ErrorAggregatorInterface;
31
use Limoncello\Validation\Contracts\Execution\ContextStorageInterface;
32
use Limoncello\Validation\Errors\Error;
33
use Limoncello\Validation\Execution\BlockInterpreter;
34
use Neomerx\JsonApi\Exceptions\JsonApiException;
35
use Neomerx\JsonApi\Http\Query\BaseQueryParser;
36
37
/**
38
 * @package Limoncello\Flute
39
 *
40
 * @SuppressWarnings(PHPMD.TooManyFields)
41
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
42
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
43
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
44
 */
45
class QueryParser extends BaseQueryParser implements JsonApiQueryValidatingParserInterface
46
{
47
    /**
48
     * @var array
49
     */
50
    private $filterParameters;
51
52
    /**
53
     * @var bool
54
     */
55
    private $areFiltersWithAnd;
56
57
    /**
58
     * @var int|null
59
     */
60
    private $pagingOffset;
61
62
    /**
63
     * @var int|null
64
     */
65
    private $pagingLimit;
66
67
    /**
68
     * NOTE: Despite the type it is just a string so only static methods can be called from the interface.
69
     *
70
     * @var JsonApiQueryRulesSerializerInterface|string
71
     */
72
    private $serializerClass;
73
74
    /**
75
     * @var array
76
     */
77
    private $serializedRuleSet;
78
79
    /**
80
     * @var array
81
     */
82
    private $validationBlocks;
83
84
    /**
85
     * @var ContextStorageInterface
86
     */
87
    private $context;
88
89
    /**
90
     * @var CaptureAggregatorInterface
91
     */
92
    private $captures;
93
94
    /**
95
     * @var ErrorAggregatorInterface
96
     */
97
    private $validationErrors;
98
99
    /**
100
     * @var JsonApiErrorCollection
101
     */
102
    private $jsonErrors;
103
104
    /**
105
     * @var null|array
106
     */
107
    private $cachedFilters = null;
108
109
    /**
110
     * @var null|array
111
     */
112
    private $cachedFields = null;
113
114
    /**
115
     * @var null|array
116
     */
117
    private $cachedSorts = null;
118
119
    /**
120
     * @var null|array
121
     */
122
    private $cachedIncludes = null;
123
124
    /**
125
     * @var FormatterFactoryInterface
126
     */
127
    private $formatterFactory;
128
129
    /**
130
     * @var FormatterInterface|null
131
     */
132
    private $formatter;
133
134
    /**
135
     * @param string                     $rulesClass
136
     * @param string                     $serializerClass
137
     * @param array                      $serializedData
138
     * @param ContextStorageInterface    $context
139
     * @param CaptureAggregatorInterface $captures
140
     * @param ErrorAggregatorInterface   $validationErrors
141
     * @param JsonApiErrorCollection     $jsonErrors
142
     * @param FormatterFactoryInterface  $formatterFactory
143
     * @param string[]|null              $messages
144
     */
145 32
    public function __construct(
146
        string $rulesClass,
147
        string $serializerClass,
148
        array $serializedData,
149
        ContextStorageInterface $context,
150
        CaptureAggregatorInterface $captures,
151
        ErrorAggregatorInterface $validationErrors,
152
        JsonApiErrorCollection $jsonErrors,
153
        FormatterFactoryInterface $formatterFactory,
154
        array $messages = null
155
    ) {
156 32
        assert(
157 32
            in_array(JsonApiQueryRulesSerializerInterface::class, class_implements($serializerClass)),
158 32
            "`$serializerClass` should implement interface `" . JsonApiQueryRulesSerializerInterface::class . '`.'
159
        );
160
161 32
        $parameters = [];
162 32
        parent::__construct($parameters, $messages);
163 32
        $this->serializerClass  = $serializerClass;
164 32
        $this->context          = $context;
165 32
        $this->captures         = $captures;
166 32
        $this->validationErrors = $validationErrors;
167 32
        $this->jsonErrors       = $jsonErrors;
168 32
        $this->formatterFactory = $formatterFactory;
169
170 32
        assert($this->serializerClass::hasRules($rulesClass, $serializedData));
0 ignored issues
show
Bug introduced by
The method hasRules cannot be called on $this->serializerClass (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
171 32
        $this->serializedRuleSet = $this->serializerClass::readRules($rulesClass, $serializedData);
0 ignored issues
show
Bug introduced by
The method readRules cannot be called on $this->serializerClass (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
172 32
        $this->validationBlocks  = $this->serializerClass::readBlocks($serializedData);
0 ignored issues
show
Bug introduced by
The method readBlocks cannot be called on $this->serializerClass (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
173
174 32
        $this->clear();
175
    }
176
177
    /**
178
     * @inheritdoc
179
     */
180 31
    public function parse(array $parameters): JsonApiQueryValidatingParserInterface
181
    {
182 31
        $this->clear();
183
184 31
        parent::setParameters($parameters);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (setParameters() instead of parse()). Are you sure this is correct? If so, you might want to change this to $this->setParameters().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
185
186 31
        $this->parsePagingParameters()->parseFilterLink();
187
188 27
        return $this;
189
    }
190
191
    /**
192
     * @inheritdoc
193
     */
194 17
    public function areFiltersWithAnd(): bool
195
    {
196 17
        return $this->areFiltersWithAnd;
197
    }
198
199
    /**
200
     * @inheritdoc
201
     */
202 1
    public function hasFilters(): bool
203
    {
204 1
        return $this->hasParameter(static::PARAM_FILTER);
205
    }
206
207
    /**
208
     * @inheritdoc
209
     */
210 14
    public function hasFields(): bool
211
    {
212 14
        return $this->hasParameter(static::PARAM_FIELDS);
213
    }
214
215
    /**
216
     * @inheritdoc
217
     */
218 14
    public function hasIncludes(): bool
219
    {
220 14
        return $this->hasParameter(static::PARAM_INCLUDE);
221
    }
222
223
    /**
224
     * @inheritdoc
225
     */
226 1
    public function hasSorts(): bool
227
    {
228 1
        return $this->hasParameter(static::PARAM_SORT);
229
    }
230
231
    /**
232
     * @inheritdoc
233
     */
234 1
    public function hasPaging(): bool
235
    {
236 1
        return $this->hasParameter(static::PARAM_PAGE);
237
    }
238
239
    /**
240
     * @inheritdoc
241
     */
242 22
    public function getFilters(): array
243
    {
244 22
        if ($this->cachedFilters === null) {
245 22
            $this->cachedFilters = $this->iterableToArray($this->getValidatedFilters());
0 ignored issues
show
Documentation introduced by
$this->getValidatedFilters() is of type object<Generator>, but the function expects a object<Limoncello\Flute\...ation\JsonApi\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
246
        }
247
248 18
        return $this->cachedFilters;
249
    }
250
251
    /**
252
     * @inheritdoc
253
     */
254 2
    public function getFields(): array
255
    {
256 2
        if ($this->cachedFields === null) {
257 2
            $this->cachedFields = $this->iterableToArray($this->getValidatedFields(parent::getFields()));
0 ignored issues
show
Documentation introduced by
parent::getFields() is of type object<Generator>, but the function expects a object<Limoncello\Flute\...ation\JsonApi\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$this->getValidatedFields(parent::getFields()) is of type object<Generator>, but the function expects a object<Limoncello\Flute\...ation\JsonApi\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
258
        }
259
260 2
        return $this->cachedFields;
261
    }
262
263
    /**
264
     * @inheritdoc
265
     */
266 16
    public function getSorts(): array
267
    {
268 16
        if ($this->cachedSorts === null) {
269 16
            $this->cachedSorts = $this->iterableToArray($this->getValidatedSorts(parent::getSorts()));
0 ignored issues
show
Documentation introduced by
parent::getSorts() is of type object<Generator>, but the function expects a object<Limoncello\Flute\...ation\JsonApi\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$this->getValidatedSorts(parent::getSorts()) is of type object<Generator>, but the function expects a object<Limoncello\Flute\...ation\JsonApi\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
270
        }
271
272 16
        return $this->cachedSorts;
273
    }
274
275
    /**
276
     * @inheritdoc
277
     */
278 16
    public function getIncludes(): iterable
279
    {
280 16
        if ($this->cachedIncludes === null) {
281 16
            $this->cachedIncludes = $this->iterableToArray($this->getValidatedIncludes(parent::getIncludes()));
0 ignored issues
show
Documentation introduced by
parent::getIncludes() is of type object<Generator>, but the function expects a object<Limoncello\Flute\...ation\JsonApi\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$this->getValidatedInclu...(parent::getIncludes()) is of type object<Generator>, but the function expects a object<Limoncello\Flute\...ation\JsonApi\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
282
        }
283
284 16
        return $this->cachedIncludes;
285
    }
286
287
    /**
288
     * @inheritdoc
289
     */
290 17
    public function getPagingOffset(): ?int
291
    {
292 17
        return $this->pagingOffset;
293
    }
294
295
    /**
296
     * @inheritdoc
297
     */
298 17
    public function getPagingLimit(): ?int
299
    {
300 17
        return $this->pagingLimit;
301
    }
302
303
    /**
304
     * @return JsonApiErrorCollection
305
     */
306 6
    protected function getJsonErrors(): JsonApiErrorCollection
307
    {
308 6
        return $this->jsonErrors;
309
    }
310
311
    /**
312
     * @return ErrorAggregatorInterface
313
     */
314 32
    protected function getValidationErrors(): ErrorAggregatorInterface
315
    {
316 32
        return $this->validationErrors;
317
    }
318
319
    /**
320
     * @return CaptureAggregatorInterface
321
     */
322 32
    protected function getCaptures(): CaptureAggregatorInterface
323
    {
324 32
        return $this->captures;
325
    }
326
327
    /**
328
     * @return ContextStorageInterface
329
     */
330 30
    protected function getContext(): ContextStorageInterface
331
    {
332 30
        return $this->context;
333
    }
334
335
    /**
336
     * @return array
337
     */
338 30
    protected function getValidationBlocks(): array
339
    {
340 30
        return $this->validationBlocks;
341
    }
342
343
    /**
344
     * @return array
345
     */
346 31
    protected function getSerializedRuleSet(): array
347
    {
348 31
        return $this->serializedRuleSet;
349
    }
350
351
    /**
352
     * @return FormatterFactoryInterface
353
     */
354 1
    protected function getFormatterFactory(): FormatterFactoryInterface
355
    {
356 1
        return $this->formatterFactory;
357
    }
358
359
    /**
360
     * @return FormatterInterface
361
     */
362 1
    protected function getFormatter(): FormatterInterface
363
    {
364 1
        if ($this->formatter === null) {
365 1
            $this->formatter = $this->getFormatterFactory()->createFormatter(FluteSettings::VALIDATION_NAMESPACE);
366
        }
367
368 1
        return $this->formatter;
369
    }
370
371
    /**
372
     * @param string $name
373
     *
374
     * @return bool
375
     */
376 17
    protected function hasParameter(string $name): bool
377
    {
378 17
        return array_key_exists($name, $this->getParameters());
379
    }
380
381
    /**
382
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be Generator?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
383
     *
384
     * @SuppressWarnings(PHPMD.ElseExpression)
385
     */
386 22
    private function getValidatedFilters(): iterable
387
    {
388 22
        $ruleIndexes = $this->serializerClass::readFilterRulesIndexes($this->getSerializedRuleSet());
389
390 22
        if ($ruleIndexes === null) {
391
            // without validation
392 1
            foreach ($this->getFilterParameters() as $field => $operationsWithArgs) {
393 1
                yield $field => $this->parseOperationsAndArguments(static::PARAM_FILTER, $operationsWithArgs);
394
            }
395
        } else {
396
            // with validation
397 21
            $mainIndexes = $this->serializerClass::readRuleMainIndexes($ruleIndexes);
398 21
            $this->validationStarts(static::PARAM_FILTER, $ruleIndexes);
399 21
            foreach ($this->getFilterParameters() as $field => $operationsWithArgs) {
400 13
                if (is_string($field) === false || empty($field) === true ||
401 13
                    is_array($operationsWithArgs) === false || empty($operationsWithArgs) === true
402
                ) {
403 1
                    throw new InvalidQueryParametersException($this->createParameterError(static::PARAM_FILTER));
404
                }
405
406 12
                if (array_key_exists($field, $mainIndexes) === false) {
407
                    // unknown field set type
408 2
                    $this->getValidationErrors()->add(
409 2
                        new Error(static::PARAM_FILTER, $field, ErrorCodes::INVALID_VALUE, null)
410
                    );
411
                } else {
412
                    // for field a validation rule is defined so input value will be validated
413 10
                    $ruleIndex = $mainIndexes[$field];
414 10
                    $parsed    = $this->parseOperationsAndArguments(static::PARAM_FILTER, $operationsWithArgs);
415
416 12
                    yield $field => $this->validateFilterArguments($ruleIndex, $parsed);
417
                }
418
            }
419 19
            $this->validateEnds(static::PARAM_FILTER, $ruleIndexes);
420
        }
421
    }
422
423
    /**
424
     * @param iterable $fieldsFromParent
425
     *
426
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be Generator?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
427
     *
428
     * @SuppressWarnings(PHPMD.StaticAccess)
429
     * @SuppressWarnings(PHPMD.ElseExpression)
430
     */
431 2
    private function getValidatedFields(iterable $fieldsFromParent): iterable
432
    {
433 2
        $ruleIndexes = $this->serializerClass::readFieldSetRulesIndexes($this->getSerializedRuleSet());
434
435 2
        if ($ruleIndexes === null) {
436
            // without validation
437 1
            foreach ($fieldsFromParent as $type => $fieldList) {
438 1
                yield $type => $fieldList;
439
            }
440
        } else {
441
            // with validation
442 1
            $mainIndexes = $this->serializerClass::readRuleMainIndexes($ruleIndexes);
443 1
            $this->validationStarts(static::PARAM_FIELDS, $ruleIndexes);
444 1
            foreach (parent::getFields() as $type => $fieldList) {
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (getFields() instead of getValidatedFields()). Are you sure this is correct? If so, you might want to change this to $this->getFields().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
445 1
                if (array_key_exists($type, $mainIndexes) === true) {
446 1
                    yield $type => $this->validateValues($mainIndexes[$type], $fieldList);
447
                } else {
448
                    // unknown field set type
449 1
                    $this->getValidationErrors()->add(
450 1
                        new Error(static::PARAM_FIELDS, $type, ErrorCodes::INVALID_VALUE, null)
451
                    );
452
                }
453
            }
454 1
            $this->validateEnds(static::PARAM_FIELDS, $ruleIndexes);
455
        }
456
    }
457
458
    /**
459
     * @param iterable $sortsFromParent
460
     *
461
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be Generator?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
462
     *
463
     * @SuppressWarnings(PHPMD.StaticAccess)
464
     * @SuppressWarnings(PHPMD.ElseExpression)
465
     */
466 16
    private function getValidatedSorts(iterable $sortsFromParent): iterable
467
    {
468 16
        $ruleIndexes = $this->serializerClass::readSortsRuleIndexes($this->getSerializedRuleSet());
469
470 16
        if ($ruleIndexes === null) {
471
            // without validation
472 2
            foreach (parent::getSorts() as $field => $isAsc) {
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (getSorts() instead of getValidatedSorts()). Are you sure this is correct? If so, you might want to change this to $this->getSorts().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
473 2
                yield $field => $isAsc;
474
            }
475
        } else {
476
            // with validation
477 14
            $ruleIndex = $this->serializerClass::readRuleMainIndex($ruleIndexes);
478 14
            $this->validationStarts(static::PARAM_SORT, $ruleIndexes);
479 14
            foreach ($sortsFromParent as $field => $isAsc) {
480 5
                $this->getCaptures()->clear();
481 5
                $this->validateAndAccumulateError($field, $ruleIndex);
482 5
                if ($this->getCaptures()->count() > 0) {
483 5
                    yield $this->readSingleCapturedValue() => $isAsc;
484
                }
485
            }
486 14
            $this->validateEnds(static::PARAM_SORT, $ruleIndexes);
487
        }
488
    }
489
490
    /**
491
     * @param iterable $includesFromParent
492
     *
493
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be Generator?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
494
     *
495
     * @SuppressWarnings(PHPMD.StaticAccess)
496
     * @SuppressWarnings(PHPMD.ElseExpression)
497
     */
498 16
    private function getValidatedIncludes(iterable $includesFromParent): iterable
499
    {
500 16
        $ruleIndexes = $this->serializerClass::readIncludesRuleIndexes($this->getSerializedRuleSet());
501
502 16
        if ($ruleIndexes === null) {
503
            // without validation
504 2
            foreach (parent::getIncludes() as $path => $split) {
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (getIncludes() instead of getValidatedIncludes()). Are you sure this is correct? If so, you might want to change this to $this->getIncludes().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
505 2
                yield $path => $split;
506
            }
507
        } else {
508
            // with validation
509 14
            $ruleIndex = $this->serializerClass::readRuleMainIndex($ruleIndexes);
510 14
            $this->validationStarts(static::PARAM_INCLUDE, $ruleIndexes);
511 14
            foreach ($includesFromParent as $path => $split) {
512 4
                $this->getCaptures()->clear();
513 4
                $this->validateAndAccumulateError($path, $ruleIndex);
514 4
                if ($this->getCaptures()->count() > 0) {
515 4
                    yield $this->readSingleCapturedValue() => $split;
516
                }
517
            }
518 14
            $this->validateEnds(static::PARAM_INCLUDE, $ruleIndexes);
519
        }
520
    }
521
522
    /**
523
     * @param iterable $iterable
524
     *
525
     * @return array
526
     */
527 25
    private function iterableToArray(iterable $iterable): array
528
    {
529 25
        $result = [];
530
531 25
        foreach ($iterable as $key => $value) {
532 19
            $result[$key] = $value instanceof Generator ? $this->iterableToArray($value) : $value;
533
        }
534
535 21
        return $result;
536
    }
537
538
    /**
539
     * @param mixed $value
540
     *
541
     * @return int
542
     */
543 31
    private function validatePageOffset($value): int
544
    {
545 31
        $ruleIndexes    = $this->serializerClass::readPageOffsetRuleIndexes($this->getSerializedRuleSet());
546 31
        $validatedValue = $this->validatePaginationValue($value, $ruleIndexes);
547
548 31
        return $validatedValue;
549
    }
550
551
    /**
552
     * @param mixed $value
553
     *
554
     * @return int
555
     */
556 31
    private function validatePageLimit($value): int
557
    {
558 31
        $ruleIndexes    = $this->serializerClass::readPageLimitRuleIndexes($this->getSerializedRuleSet());
559 31
        $validatedValue = $this->validatePaginationValue($value, $ruleIndexes);
560
561 31
        return $validatedValue;
562
    }
563
564
    /**
565
     * @param mixed $value
566
     * @param array $ruleIndexes
567
     *
568
     * @return int
569
     */
570 31
    private function validatePaginationValue($value, ?array $ruleIndexes): int
571
    {
572
        // no validation rule means we should accept any input value
573 31
        if ($ruleIndexes === null) {
574 1
            return is_numeric($value) === true ? (int)$value : 0;
575
        }
576
577 30
        $ruleIndex = $this->serializerClass::readRuleMainIndex($ruleIndexes);
578
579 30
        $this->validationStarts(static::PARAM_PAGE, $ruleIndexes);
580 30
        $this->validateAndThrowOnError(static::PARAM_PAGE, $value, $ruleIndex);
581 30
        $this->validateEnds(static::PARAM_PAGE, $ruleIndexes);
582
583 30
        $validatedValue = $this->readSingleCapturedValue();
584
585 30
        return (int)$validatedValue;
586
    }
587
588
    /**
589
     * @param string $paramName
590
     * @param array  $ruleIndexes
591
     *
592
     * @return void
593
     *
594
     * @SuppressWarnings(PHPMD.StaticAccess)
595
     */
596 30
    private function validationStarts(string $paramName, array $ruleIndexes): void
597
    {
598 30
        $this->getCaptures()->clear();
599 30
        $this->getValidationErrors()->clear();
600
601 30
        BlockInterpreter::executeStarts(
602 30
            $this->serializerClass::readRuleStartIndexes($ruleIndexes),
603 30
            $this->getValidationBlocks(),
604 30
            $this->getContext(),
605 30
            $this->getValidationErrors()
606
        );
607 30
        $this->checkValidationQueueErrors($paramName);
608
    }
609
610
    /**
611
     * @param string $paramName
612
     * @param mixed  $value
613
     * @param int    $ruleIndex
614
     *
615
     * @return void
616
     *
617
     * @SuppressWarnings(PHPMD.StaticAccess)
618
     */
619 30
    private function validateAndThrowOnError(string $paramName, $value, int $ruleIndex): void
620
    {
621 30
        BlockInterpreter::executeBlock(
622 30
            $value,
623 30
            $ruleIndex,
624 30
            $this->getValidationBlocks(),
625 30
            $this->getContext(),
626 30
            $this->getCaptures(),
627 30
            $this->getValidationErrors()
628
        );
629 30
        $this->checkValidationQueueErrors($paramName);
630
    }
631
632
    /**
633
     * @param mixed $value
634
     * @param int   $ruleIndex
635
     *
636
     * @return bool
637
     *
638
     * @SuppressWarnings(PHPMD.StaticAccess)
639
     */
640 16
    private function validateAndAccumulateError($value, int $ruleIndex): bool
641
    {
642 16
        return BlockInterpreter::executeBlock(
643 16
            $value,
644 16
            $ruleIndex,
645 16
            $this->getValidationBlocks(),
646 16
            $this->getContext(),
647 16
            $this->getCaptures(),
648 16
            $this->getValidationErrors()
649
        );
650
    }
651
652
    /**
653
     * @param string $paramName
654
     * @param array  $ruleIndexes
655
     *
656
     * @return void
657
     *
658
     * @SuppressWarnings(PHPMD.StaticAccess)
659
     */
660 30
    private function validateEnds(string $paramName, array $ruleIndexes): void
661
    {
662 30
        BlockInterpreter::executeEnds(
663 30
            $this->serializerClass::readRuleEndIndexes($ruleIndexes),
664 30
            $this->getValidationBlocks(),
665 30
            $this->getContext(),
666 30
            $this->getValidationErrors()
667
        );
668 30
        $this->checkValidationQueueErrors($paramName);
669
    }
670
671
    /**
672
     * @return mixed
673
     */
674 30
    private function readSingleCapturedValue()
675
    {
676 30
        assert(count($this->getCaptures()->get()) === 1, 'Expected that only one value would be captured.');
677 30
        $value = current($this->getCaptures()->get());
678
679 30
        return $value;
680
    }
681
682
    /**
683
     * @param int      $ruleIndex
684
     * @param iterable $values
685
     *
686
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be Generator?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
687
     */
688 10
    private function validateValues(int $ruleIndex, iterable $values): iterable
689
    {
690 10
        foreach ($values as $key => $value) {
691 9
            $this->getCaptures()->clear();
692 9
            $this->validateAndAccumulateError($value, $ruleIndex);
693 9
            if ($this->getCaptures()->count() > 0) {
694 9
                yield $key => $this->readSingleCapturedValue();
695
            }
696
        }
697
    }
698
699
    /**
700
     * @param int      $ruleIndex
701
     * @param iterable $opsAndArgs
702
     *
703
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be Generator?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
704
     */
705 10
    private function validateFilterArguments(int $ruleIndex, iterable $opsAndArgs): iterable
706
    {
707 10
        foreach ($opsAndArgs as $operation => $arguments) {
708 9
            yield $operation => $this->validateValues($ruleIndex, $arguments);
709
        }
710
    }
711
712
    /**
713
     * @return self
714
     */
715 31
    private function parsePagingParameters(): self
716
    {
717 31
        $parameters    = $this->getParameters();
718 31
        $mightBeOffset = $parameters[static::PARAM_PAGE][static::PARAM_PAGING_OFFSET] ?? null;
719 31
        $mightBeLimit  = $parameters[static::PARAM_PAGE][static::PARAM_PAGING_LIMIT] ?? null;
720
721 31
        $this->pagingOffset = $this->validatePageOffset($mightBeOffset);
722 31
        $this->pagingLimit  = $this->validatePageLimit($mightBeLimit);
723
724 31
        assert(is_int($this->pagingOffset) === true && $this->pagingOffset >= 0);
725 31
        assert(is_int($this->pagingLimit) === true && $this->pagingLimit > 0);
726
727 31
        return $this;
728
    }
729
730
    /**
731
     * @param string $paramName
732
     *
733
     * @return void
734
     *
735
     * @throws JsonApiException
736
     */
737 30
    private function checkValidationQueueErrors(string $paramName): void
738
    {
739 30
        if ($this->getValidationErrors()->count() > 0) {
740 6
            foreach ($this->getValidationErrors()->get() as $error) {
741 6
                $this->getJsonErrors()->addValidationQueryError($paramName, $error);
742
            }
743
744 6
            throw new JsonApiException($this->getJsonErrors());
745
        }
746
    }
747
748
    /**
749
     * @param array $values
750
     *
751
     * @return self
752
     */
753 27
    private function setFilterParameters(array $values): self
754
    {
755 27
        $this->filterParameters = $values;
756
757 27
        return $this;
758
    }
759
760
    /**
761
     * @return array
762
     */
763 22
    private function getFilterParameters(): array
764
    {
765 22
        return $this->filterParameters;
766
    }
767
768
    /**
769
     * @return self
770
     */
771 25
    private function setFiltersWithAnd(): self
772
    {
773 25
        $this->areFiltersWithAnd = true;
774
775 25
        return $this;
776
    }
777
778
    /**
779
     * @return self
780
     */
781 2
    private function setFiltersWithOr(): self
782
    {
783 2
        $this->areFiltersWithAnd = false;
784
785 2
        return $this;
786
    }
787
788
    /**
789
     * @return self
790
     */
791 32
    private function clear(): self
792
    {
793 32
        $this->filterParameters  = [];
794 32
        $this->areFiltersWithAnd = true;
795 32
        $this->pagingOffset      = null;
796 32
        $this->pagingLimit       = null;
797
798 32
        $this->cachedFilters  = null;
799 32
        $this->cachedFields   = null;
800 32
        $this->cachedIncludes = null;
801 32
        $this->cachedSorts    = null;
802
803 32
        $this->getCaptures()->clear();
804 32
        $this->getValidationErrors()->clear();
805
806 32
        return $this;
807
    }
808
809
    /**
810
     * Pre-parsing for filter parameters.
811
     *
812
     * @return self
813
     *
814
     * @SuppressWarnings(PHPMD.ElseExpression)
815
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
816
     */
817 31
    private function parseFilterLink(): self
818
    {
819 31
        if (array_key_exists(static::PARAM_FILTER, $this->getParameters()) === false) {
820 13
            $this->setFiltersWithAnd()->setFilterParameters([]);
821
822 13
            return $this;
823
        }
824
825 18
        $filterSection = $this->getParameters()[static::PARAM_FILTER];
826 18
        if (is_array($filterSection) === false || empty($filterSection) === true) {
827 2
            throw new InvalidQueryParametersException($this->createParameterError(static::PARAM_FILTER));
828
        }
829
830 16
        $isWithAnd = true;
831 16
        reset($filterSection);
832
833
        // check if top level element is `AND` or `OR`
834 16
        $firstKey   = key($filterSection);
835 16
        $firstLcKey = strtolower(trim($firstKey));
836 16
        if (($hasOr = ($firstLcKey === 'or')) || $firstLcKey === 'and') {
837 4
            if (count($filterSection) > 1 ||
838 4
                empty($filterSection = $filterSection[$firstKey]) === true ||
839 4
                is_array($filterSection) === false
840
            ) {
841 2
                throw new InvalidQueryParametersException($this->createParameterError(static::PARAM_FILTER));
842
            } else {
843 2
                $this->setFilterParameters($filterSection);
844 2
                if ($hasOr === true) {
845 2
                    $isWithAnd = false;
846
                }
847
            }
848
        } else {
849 12
            $this->setFilterParameters($filterSection);
850
        }
851
852 14
        $isWithAnd === true ? $this->setFiltersWithAnd() : $this->setFiltersWithOr();
853
854 14
        return $this;
855
    }
856
857
    /**
858
     * @param string $parameterName
859
     * @param array  $value
860
     *
861
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be Generator?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
862
     *
863
     * @SuppressWarnings(PHPMD.ElseExpression)
864
     */
865 11
    private function parseOperationsAndArguments(string $parameterName, array $value): iterable
866
    {
867
        // in this case we interpret it as an [operation => 'comma separated argument(s)']
868 11
        foreach ($value as $operationName => $arguments) {
869 11
            if (is_string($operationName) === false || empty($operationName) === true ||
870 11
                is_string($arguments) === false
871
            ) {
872 1
                $title = $this->getFormatter()->formatMessage(Validation::INVALID_OPERATION_ARGUMENTS);
873 1
                $error = $this->createQueryError($parameterName, $title);
874 1
                throw new InvalidQueryParametersException($error);
875
            }
876
877 10
            if ($arguments === '') {
878 1
                yield $operationName => [];
879
            } else {
880 10
                yield $operationName => $this->splitCommaSeparatedStringAndCheckNoEmpties($parameterName, $arguments);
881
            }
882
        }
883
    }
884
}
885