Completed
Push — develop ( 73136c...b9dc46 )
by Neomerx
15:06 queued 13:01
created

Crud::fetchResourcesWithoutRelationships()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 12
cts 12
cp 1
rs 8.9197
c 0
b 0
f 0
cc 4
eloc 17
nc 4
nop 3
crap 4
1
<?php namespace Limoncello\Flute\Api;
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 ArrayObject;
20
use Closure;
21
use Doctrine\DBAL\Connection;
22
use Doctrine\DBAL\Driver\PDOConnection;
23
use Doctrine\DBAL\Platforms\AbstractPlatform;
24
use Doctrine\DBAL\Query\QueryBuilder;
25
use Doctrine\DBAL\Types\Type;
26
use Generator;
27
use Limoncello\Container\Traits\HasContainerTrait;
28
use Limoncello\Contracts\Data\ModelSchemeInfoInterface;
29
use Limoncello\Contracts\Data\RelationshipTypes;
30
use Limoncello\Contracts\L10n\FormatterFactoryInterface;
31
use Limoncello\Flute\Adapters\ModelQueryBuilder;
32
use Limoncello\Flute\Contracts\Adapters\PaginationStrategyInterface;
33
use Limoncello\Flute\Contracts\Api\CrudInterface;
34
use Limoncello\Flute\Contracts\FactoryInterface;
35
use Limoncello\Flute\Contracts\Http\Query\FilterParameterInterface;
36
use Limoncello\Flute\Contracts\Models\ModelStorageInterface;
37
use Limoncello\Flute\Contracts\Models\PaginatedDataInterface;
38
use Limoncello\Flute\Contracts\Models\TagStorageInterface;
39
use Limoncello\Flute\Exceptions\InvalidArgumentException;
40
use Limoncello\Flute\L10n\Messages;
41
use Neomerx\JsonApi\Contracts\Document\DocumentInterface;
42
use Psr\Container\ContainerInterface;
43
use Traversable;
44
45
/**
46
 * @package Limoncello\Flute
47
 *
48
 * @SuppressWarnings(PHPMD.TooManyMethods)
49
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
50
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
51
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
52
 */
53
class Crud implements CrudInterface
54
{
55
    use HasContainerTrait;
56
57
    /** Internal constant. Path constant. */
58
    protected const ROOT_PATH = '';
59
60
    /** Internal constant. Path constant. */
61
    protected const PATH_SEPARATOR = DocumentInterface::PATH_SEPARATOR;
62
63
    /**
64
     * @var FactoryInterface
65
     */
66
    private $factory;
67
68
    /**
69
     * @var string
70
     */
71
    private $modelClass;
72
73
    /**
74
     * @var ModelSchemeInfoInterface
75
     */
76
    private $modelSchemes;
77
78
    /**
79
     * @var PaginationStrategyInterface
80
     */
81
    private $paginationStrategy;
82
83
    /**
84
     * @var Connection
85
     */
86
    private $connection;
87
88
    /**
89
     * @var iterable|null
90
     */
91
    private $filterParameters = null;
92
93
    /**
94
     * @var bool
95
     */
96
    private $areFiltersWithAnd = true;
97
98
    /**
99
     * @var iterable|null
100
     */
101
    private $sortingParameters = null;
102
103
    /**
104
     * @var array
105
     */
106
    private $relFiltersAndSorts = [];
107
108
    /**
109
     * @var iterable|null
110
     */
111
    private $includePaths = null;
112
113
    /**
114
     * @var int|null
115
     */
116
    private $pagingOffset = null;
117
118
    /**
119
     * @var Closure|null
120
     */
121
    private $columnMapper = null;
122
123
    /**
124
     * @var bool
125
     */
126
    private $isFetchTyped;
127
128
    /**
129
     * @var int|null
130
     */
131
    private $pagingLimit = null;
132
133
    /** internal constant */
134
    private const REL_FILTERS_AND_SORTS__FILTERS = 0;
135
136
    /** internal constant */
137
    private const REL_FILTERS_AND_SORTS__SORTS = 1;
138
139
    /**
140
     * @param ContainerInterface $container
141
     * @param string             $modelClass
142
     */
143 53
    public function __construct(ContainerInterface $container, string $modelClass)
144
    {
145 53
        $this->setContainer($container);
146
147 53
        $this->modelClass         = $modelClass;
148 53
        $this->factory            = $this->getContainer()->get(FactoryInterface::class);
149 53
        $this->modelSchemes       = $this->getContainer()->get(ModelSchemeInfoInterface::class);
150 53
        $this->paginationStrategy = $this->getContainer()->get(PaginationStrategyInterface::class);
151 53
        $this->connection         = $this->getContainer()->get(Connection::class);
152
153 53
        $this->clearBuilderParameters()->clearFetchParameters();
154
    }
155
156
    /**
157
     * @param Closure $mapper
158
     *
159
     * @return self
160
     */
161 1
    public function withColumnMapper(Closure $mapper): self
162
    {
163 1
        $this->columnMapper = $mapper;
164
165 1
        return $this;
166
    }
167
168
    /**
169
     * @inheritdoc
170
     */
171 43
    public function withFilters(iterable $filterParameters): CrudInterface
172
    {
173 43
        $this->filterParameters = $filterParameters;
0 ignored issues
show
Documentation Bug introduced by
It seems like $filterParameters of type object<Limoncello\Flute\Contracts\Api\iterable> is incompatible with the declared type object<Limoncello\Flute\Api\iterable>|null of property $filterParameters.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
174
175 43
        return $this;
176
    }
177
178
    /**
179
     * @inheritdoc
180
     */
181 23
    public function withIndexFilter($index): CrudInterface
182
    {
183 23
        if (is_int($index) === false && is_string($index) === false) {
184 3
            throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT));
185
        }
186
187 20
        $pkName = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
188 20
        $this->withFilters([
0 ignored issues
show
Documentation introduced by
array($pkName => array(\...UALS => array($index))) is of type array<string,array<strin...0":"integer|string"}>>>, but the function expects a object<Limoncello\Flute\Contracts\Api\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...
189
            $pkName => [
190 20
                FilterParameterInterface::OPERATION_EQUALS => [$index],
191
            ],
192
        ]);
193
194 20
        return $this;
195
    }
196
197
    /**
198
     * @inheritdoc
199
     */
200 4
    public function withRelationshipFilters(string $name, iterable $filters): CrudInterface
201
    {
202 4
        $this->relFiltersAndSorts[$name][self::REL_FILTERS_AND_SORTS__FILTERS] = $filters;
203
204 4
        return $this;
205
    }
206
207
    /**
208
     * @inheritdoc
209
     */
210 1
    public function withRelationshipSorts(string $name, iterable $sorts): CrudInterface
211
    {
212 1
        $this->relFiltersAndSorts[$name][self::REL_FILTERS_AND_SORTS__SORTS] = $sorts;
213
214 1
        return $this;
215
    }
216
217
    /**
218
     * @inheritdoc
219
     */
220 15
    public function combineWithAnd(): CrudInterface
221
    {
222 15
        $this->areFiltersWithAnd = true;
223
224 15
        return $this;
225
    }
226
227
    /**
228
     * @inheritdoc
229
     */
230 2
    public function combineWithOr(): CrudInterface
231
    {
232 2
        $this->areFiltersWithAnd = false;
233
234 2
        return $this;
235
    }
236
237
    /**
238
     * @return bool
239
     */
240 32
    private function hasColumnMapper(): bool
241
    {
242 32
        return $this->columnMapper !== null;
243
    }
244
245
    /**
246
     * @return Closure
247
     */
248 1
    private function getColumnMapper(): Closure
249
    {
250 1
        return $this->columnMapper;
251
    }
252
253
    /**
254
     * @return bool
255
     */
256 44
    private function hasFilters(): bool
257
    {
258 44
        return empty($this->filterParameters) === false;
259
    }
260
261
    /**
262
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be iterable|null?

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...
263
     */
264 35
    private function getFilters(): iterable
265
    {
266 35
        return $this->filterParameters;
267
    }
268
269
    /**
270
     * @return bool
271
     */
272 35
    private function areFiltersWithAnd(): bool
273
    {
274 35
        return $this->areFiltersWithAnd;
275
    }
276
277
    /**
278
     * @inheritdoc
279
     */
280 18
    public function withSorts(iterable $sortingParameters): CrudInterface
281
    {
282 18
        $this->sortingParameters = $sortingParameters;
0 ignored issues
show
Documentation Bug introduced by
It seems like $sortingParameters of type object<Limoncello\Flute\Contracts\Api\iterable> is incompatible with the declared type object<Limoncello\Flute\Api\iterable>|null of property $sortingParameters.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
283
284 18
        return $this;
285
    }
286
287
    /**
288
     * @return bool
289
     */
290 32
    private function hasSorts(): bool
291
    {
292 32
        return empty($this->sortingParameters) === false;
293
    }
294
295
    /**
296
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be iterable|null?

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...
297
     */
298 14
    private function getSorts(): ?iterable
299
    {
300 14
        return $this->sortingParameters;
301
    }
302
303
    /**
304
     * @inheritdoc
305
     */
306 21
    public function withIncludes(iterable $includePaths): CrudInterface
307
    {
308 21
        $this->includePaths = $includePaths;
0 ignored issues
show
Documentation Bug introduced by
It seems like $includePaths of type object<Limoncello\Flute\Contracts\Api\iterable> is incompatible with the declared type object<Limoncello\Flute\Api\iterable>|null of property $includePaths.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
309
310 21
        return $this;
311
    }
312
313
    /**
314
     * @return bool
315
     */
316 36
    private function hasIncludes(): bool
317
    {
318 36
        return empty($this->includePaths) === false;
319
    }
320
321
    /**
322
     * @return iterable
0 ignored issues
show
Documentation introduced by
Should the return type not be iterable|null?

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...
323
     */
324 21
    private function getIncludes(): iterable
325
    {
326 21
        return $this->includePaths;
327
    }
328
329
    /**
330
     * @inheritdoc
331
     */
332 19
    public function withPaging(int $offset, int $limit): CrudInterface
333
    {
334 19
        $this->pagingOffset = $offset;
335 19
        $this->pagingLimit  = $limit;
336
337 19
        return $this;
338
    }
339
340
    /**
341
     * @return self
342
     */
343 53
    public function shouldBeTyped(): self
344
    {
345 53
        $this->isFetchTyped = true;
346
347 53
        return $this;
348
    }
349
350
    /**
351
     * @return self
352
     */
353 4
    public function shouldBeUntyped(): self
354
    {
355 4
        $this->isFetchTyped = false;
356
357 4
        return $this;
358
    }
359
360
    /**
361
     * @return bool
362
     */
363 41
    private function hasPaging(): bool
364
    {
365 41
        return $this->pagingOffset !== null && $this->pagingLimit !== null;
366
    }
367
368
    /**
369
     * @return int
370
     */
371 19
    private function getPagingOffset(): int
372
    {
373 19
        return $this->pagingOffset;
374
    }
375
376
    /**
377
     * @return int
378
     */
379 19
    private function getPagingLimit(): int
380
    {
381 19
        return $this->pagingLimit;
382
    }
383
384
    /**
385
     * @return bool
386
     */
387 41
    private function isFetchTyped(): bool
388
    {
389 41
        return $this->isFetchTyped;
390
    }
391
392
    /**
393
     * @return Connection
394
     */
395 45
    protected function getConnection(): Connection
396
    {
397 45
        return $this->connection;
398
    }
399
400
    /**
401
     * @param string $modelClass
402
     *
403
     * @return ModelQueryBuilder
404
     */
405 45
    protected function createBuilder(string $modelClass): ModelQueryBuilder
406
    {
407 45
        return $this->createBuilderFromConnection($this->getConnection(), $modelClass);
408
    }
409
410
    /**
411
     * @param Connection $connection
412
     * @param string     $modelClass
413
     *
414
     * @return ModelQueryBuilder
415
     */
416 45
    private function createBuilderFromConnection(Connection $connection, string $modelClass): ModelQueryBuilder
417
    {
418 45
        return $this->getFactory()->createModelQueryBuilder($connection, $modelClass, $this->getModelSchemes());
419
    }
420
421
    /**
422
     * @param ModelQueryBuilder $builder
423
     *
424
     * @return Crud
425
     */
426 32
    protected function applyColumnMapper(ModelQueryBuilder $builder): self
427
    {
428 32
        if ($this->hasColumnMapper() === true) {
429 1
            $builder->setColumnToDatabaseMapper($this->getColumnMapper());
430
        }
431
432 32
        return $this;
433
    }
434
435
    /**
436
     * @param ModelQueryBuilder $builder
437
     *
438
     * @return Crud
439
     */
440 33
    protected function applyAliasFilters(ModelQueryBuilder $builder): self
441
    {
442 33
        if ($this->hasFilters() === true) {
443 24
            $filters = $this->getFilters();
444 24
            $this->areFiltersWithAnd() === true ?
445 24
                $builder->addFiltersWithAndToAlias($filters) : $builder->addFiltersWithOrToAlias($filters);
0 ignored issues
show
Bug introduced by
It seems like $filters defined by $this->getFilters() on line 443 can also be of type null; however, Limoncello\Flute\Adapter...FiltersWithAndToAlias() does only seem to accept object<Limoncello\Flute\Adapters\iterable>, 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...
Bug introduced by
It seems like $filters defined by $this->getFilters() on line 443 can also be of type null; however, Limoncello\Flute\Adapter...dFiltersWithOrToAlias() does only seem to accept object<Limoncello\Flute\Adapters\iterable>, 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...
446
        }
447
448 33
        return $this;
449
    }
450
451
    /**
452
     * @param ModelQueryBuilder $builder
453
     *
454
     * @return self
455
     */
456 5
    protected function applyTableFilters(ModelQueryBuilder $builder): self
457
    {
458 5
        if ($this->hasFilters() === true) {
459 5
            $filters = $this->getFilters();
460 5
            $this->areFiltersWithAnd() === true ?
461 5
                $builder->addFiltersWithAndToTable($filters) : $builder->addFiltersWithOrToTable($filters);
0 ignored issues
show
Bug introduced by
It seems like $filters defined by $this->getFilters() on line 459 can also be of type null; however, Limoncello\Flute\Adapter...FiltersWithAndToTable() does only seem to accept object<Limoncello\Flute\Adapters\iterable>, 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...
Bug introduced by
It seems like $filters defined by $this->getFilters() on line 459 can also be of type null; however, Limoncello\Flute\Adapter...dFiltersWithOrToTable() does only seem to accept object<Limoncello\Flute\Adapters\iterable>, 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...
462
        }
463
464 5
        return $this;
465
    }
466
467
    /**
468
     * @param ModelQueryBuilder $builder
469
     *
470
     * @return self
471
     */
472 32
    protected function applyRelationshipFiltersAndSorts(ModelQueryBuilder $builder): self
473
    {
474
        // While joining tables we select distinct rows. This flag used to apply `distinct` no more than once.
475 32
        $distinctApplied = false;
476
477 32
        foreach ($this->relFiltersAndSorts as $relationshipName => $filtersAndSorts) {
478 4
            assert(is_string($relationshipName) === true && is_array($filtersAndSorts) === true);
479 4
            $builder->addRelationshipFiltersAndSortsWithAnd(
480 4
                $relationshipName,
481 4
                $filtersAndSorts[self::REL_FILTERS_AND_SORTS__FILTERS] ?? [],
482 4
                $filtersAndSorts[self::REL_FILTERS_AND_SORTS__SORTS] ?? []
483
            );
484
485 4
            if ($distinctApplied === false) {
486 4
                $builder->distinct();
487 4
                $distinctApplied = true;
488
            }
489
        }
490
491 32
        return $this;
492
    }
493
494
    /**
495
     * @param ModelQueryBuilder $builder
496
     *
497
     * @return self
498
     */
499 32
    protected function applySorts(ModelQueryBuilder $builder): self
500
    {
501 32
        if ($this->hasSorts() === true) {
502 4
            $builder->addSorts($this->getSorts());
0 ignored issues
show
Bug introduced by
It seems like $this->getSorts() targeting Limoncello\Flute\Api\Crud::getSorts() can also be of type null; however, Limoncello\Flute\Adapter...ueryBuilder::addSorts() does only seem to accept object<Limoncello\Flute\Adapters\iterable>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
503
        }
504
505 32
        return $this;
506
    }
507
508
    /**
509
     * @param ModelQueryBuilder $builder
510
     *
511
     * @return self
512
     */
513 41
    protected function applyPaging(ModelQueryBuilder $builder): self
514
    {
515 41
        if ($this->hasPaging() === true) {
516 19
            $builder->setFirstResult($this->getPagingOffset());
517 19
            $builder->setMaxResults($this->getPagingLimit() + 1);
518
        }
519
520 41
        return $this;
521
    }
522
523
    /**
524
     * @return self
525
     */
526 53
    protected function clearBuilderParameters(): self
527
    {
528 53
        $this->columnMapper       = null;
529 53
        $this->filterParameters   = null;
530 53
        $this->areFiltersWithAnd  = true;
531 53
        $this->sortingParameters  = null;
532 53
        $this->pagingOffset       = null;
533 53
        $this->pagingLimit        = null;
534 53
        $this->relFiltersAndSorts = [];
535
536 53
        return $this;
537
    }
538
539
    /**
540
     * @return self
541
     */
542 53
    private function clearFetchParameters(): self
543
    {
544 53
        $this->includePaths = null;
545 53
        $this->shouldBeTyped();
546
547 53
        return $this;
548
    }
549
550
    /**
551
     * @param ModelQueryBuilder $builder
552
     *
553
     * @return ModelQueryBuilder
554
     */
555 1
    protected function builderOnCount(ModelQueryBuilder $builder): ModelQueryBuilder
556
    {
557 1
        return $builder;
558
    }
559
560
    /**
561
     * @param ModelQueryBuilder $builder
562
     *
563
     * @return ModelQueryBuilder
564
     */
565 32
    protected function builderOnIndex(ModelQueryBuilder $builder): ModelQueryBuilder
566
    {
567 32
        return $builder;
568
    }
569
570
    /**
571
     * @param ModelQueryBuilder $builder
572
     *
573
     * @return ModelQueryBuilder
574
     */
575 10
    protected function builderOnReadRelationship(ModelQueryBuilder $builder): ModelQueryBuilder
576
    {
577 10
        return $builder;
578
    }
579
580
    /**
581
     * @param ModelQueryBuilder $builder
582
     *
583
     * @return ModelQueryBuilder
584
     */
585 4
    protected function builderSaveResourceOnCreate(ModelQueryBuilder $builder): ModelQueryBuilder
586
    {
587 4
        return $builder;
588
    }
589
590
    /**
591
     * @param ModelQueryBuilder $builder
592
     *
593
     * @return ModelQueryBuilder
594
     */
595 4
    protected function builderSaveResourceOnUpdate(ModelQueryBuilder $builder): ModelQueryBuilder
596
    {
597 4
        return $builder;
598
    }
599
600
    /**
601
     * @param string            $relationshipName
602
     * @param ModelQueryBuilder $builder
603
     *
604
     * @return ModelQueryBuilder
605
     *
606
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
607
     */
608 2
    protected function builderSaveRelationshipOnCreate(/** @noinspection PhpUnusedParameterInspection */
609
        $relationshipName,
0 ignored issues
show
Unused Code introduced by
The parameter $relationshipName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
610
        ModelQueryBuilder $builder
611
    ): ModelQueryBuilder {
612 2
        return $builder;
613
    }
614
615
    /**
616
     * @param string            $relationshipName
617
     * @param ModelQueryBuilder $builder
618
     *
619
     * @return ModelQueryBuilder
620
     *
621
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
622
     */
623 2
    protected function builderSaveRelationshipOnUpdate(/** @noinspection PhpUnusedParameterInspection */
624
        $relationshipName,
0 ignored issues
show
Unused Code introduced by
The parameter $relationshipName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
625
        ModelQueryBuilder $builder
626
    ): ModelQueryBuilder {
627 2
        return $builder;
628
    }
629
630
    /**
631
     * @param string            $relationshipName
632
     * @param ModelQueryBuilder $builder
633
     *
634
     * @return ModelQueryBuilder
635
     *
636
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
637
     */
638 2
    protected function builderCleanRelationshipOnUpdate(/** @noinspection PhpUnusedParameterInspection */
639
        $relationshipName,
0 ignored issues
show
Unused Code introduced by
The parameter $relationshipName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
640
        ModelQueryBuilder $builder
641
    ): ModelQueryBuilder {
642 2
        return $builder;
643
    }
644
645
    /**
646
     * @param ModelQueryBuilder $builder
647
     *
648
     * @return ModelQueryBuilder
649
     */
650 5
    protected function builderOnDelete(ModelQueryBuilder $builder): ModelQueryBuilder
651
    {
652 5
        return $builder;
653
    }
654
655
    /**
656
     * @param PaginatedDataInterface|mixed|null $data
657
     *
658
     * @return void
659
     *
660
     * @SuppressWarnings(PHPMD.ElseExpression)
661
     */
662 21
    private function loadRelationships($data): void
663
    {
664 21
        $isPaginated = $data instanceof PaginatedDataInterface;
665 21
        $hasData     = ($isPaginated === true && empty($data->getData()) === false) ||
666 21
            ($isPaginated === false && $data !== null);
667
668 21
        if ($hasData === true && $this->hasIncludes() === true) {
669 21
            $modelStorage = $this->getFactory()->createModelStorage($this->getModelSchemes());
670 21
            $modelsAtPath = $this->getFactory()->createTagStorage();
671
672
            // we gonna send these objects via function params so it is an equivalent for &array
673 21
            $classAtPath = new ArrayObject();
674 21
            $idsAtPath   = new ArrayObject();
675
676 21
            $registerModelAtRoot = function ($model) use ($modelStorage, $modelsAtPath, $idsAtPath): void {
677 21
                self::registerModelAtPath(
678 21
                    $model,
679 21
                    static::ROOT_PATH,
680 21
                    $this->getModelSchemes(),
681 21
                    $modelStorage,
682 21
                    $modelsAtPath,
683 21
                    $idsAtPath
684
                );
685 21
            };
686
687 21
            $model = null;
688 21
            if ($isPaginated === true) {
689 14
                foreach ($data->getData() as $model) {
690 14
                    $registerModelAtRoot($model);
691
                }
692
            } else {
693 7
                $model = $data;
694 7
                $registerModelAtRoot($model);
695
            }
696 21
            assert($model !== null);
697 21
            $classAtPath[static::ROOT_PATH] = get_class($model);
698
699 21
            foreach ($this->getPaths($this->getIncludes()) as list ($parentPath, $childPaths)) {
0 ignored issues
show
Bug introduced by
It seems like $this->getIncludes() can be null; however, getPaths() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
700 9
                $this->loadRelationshipsLayer(
701 9
                    $modelsAtPath,
702 9
                    $classAtPath,
703 9
                    $idsAtPath,
704 9
                    $modelStorage,
705 9
                    $parentPath,
706 9
                    $childPaths
707
                );
708
            }
709
        }
710
    }
711
712
    /**
713
     * A helper to remember all model related data. Helps to ensure we consistently handle models in CRUD.
714
     *
715
     * @param mixed                    $model
716
     * @param string                   $path
717
     * @param ModelSchemeInfoInterface $modelSchemes
718
     * @param ModelStorageInterface    $modelStorage
719
     * @param TagStorageInterface      $modelsAtPath
720
     * @param ArrayObject              $idsAtPath
721
     *
722
     * @return mixed
723
     */
724 21
    private static function registerModelAtPath(
725
        $model,
726
        string $path,
727
        ModelSchemeInfoInterface $modelSchemes,
728
        ModelStorageInterface $modelStorage,
729
        TagStorageInterface $modelsAtPath,
730
        ArrayObject $idsAtPath
731
    ) {
732 21
        $uniqueModel = $modelStorage->register($model);
733 21
        if ($uniqueModel !== null) {
734 21
            $modelsAtPath->register($uniqueModel, $path);
735 21
            $pkName             = $modelSchemes->getPrimaryKey(get_class($uniqueModel));
736 21
            $modelId            = $uniqueModel->{$pkName};
737 21
            $idsAtPath[$path][] = $modelId;
738
        }
739
740 21
        return $uniqueModel;
741
    }
742
743
    /**
744
     * @param iterable $paths (string[])
745
     *
746
     * @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...
747
     */
748 21
    private static function getPaths(iterable $paths): iterable
749
    {
750
        // The idea is to normalize paths. It means build all intermediate paths.
751
        // e.g. if only `a.b.c` path it given it will be normalized to `a`, `a.b` and `a.b.c`.
752
        // Path depths store depth of each path (e.g. 0 for root, 1 for `a`, 2 for `a.b` and etc).
753
        // It is needed for yielding them in correct order (from top level to bottom).
754 21
        $normalizedPaths = [];
755 21
        $pathsDepths     = [];
756 21
        foreach ($paths as $path) {
757 9
            assert(is_array($path) || $path instanceof Traversable);
758 9
            $parentDepth = 0;
759 9
            $tmpPath     = static::ROOT_PATH;
760 9
            foreach ($path as $pathPiece) {
761 9
                assert(is_string($pathPiece));
762 9
                $parent                    = $tmpPath;
763 9
                $tmpPath                   = empty($tmpPath) === true ?
764 9
                    $pathPiece : $tmpPath . static::PATH_SEPARATOR . $pathPiece;
765 9
                $normalizedPaths[$tmpPath] = [$parent, $pathPiece];
766 9
                $pathsDepths[$parent]      = $parentDepth++;
767
            }
768
        }
769
770
        // Here we collect paths in form of parent => [list of children]
771
        // e.g. '' => ['a', 'c', 'b'], 'b' => ['bb', 'aa'] and etc
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
772 21
        $parentWithChildren = [];
773 21
        foreach ($normalizedPaths as $path => list ($parent, $childPath)) {
774 9
            $parentWithChildren[$parent][] = $childPath;
775
        }
776
777
        // And finally sort by path depth and yield parent with its children. Top level paths first then deeper ones.
778 21
        asort($pathsDepths, SORT_NUMERIC);
779 21
        foreach ($pathsDepths as $parent => $depth) {
780 9
            assert($depth !== null); // suppress unused
781 9
            $childPaths = $parentWithChildren[$parent];
782 9
            yield [$parent, $childPaths];
783
        }
784
    }
785
786
    /**
787
     * @inheritdoc
788
     */
789 2
    public function createIndexBuilder(iterable $columns = null): QueryBuilder
790
    {
791 2
        return $this->createIndexModelBuilder($columns);
792
    }
793
794
    /**
795
     * @inheritdoc
796
     */
797 5
    public function createDeleteBuilder(): QueryBuilder
798
    {
799 5
        return $this->createDeleteModelBuilder();
800
    }
801
802
    /**
803
     * @param iterable|null $columns
804
     *
805
     * @return ModelQueryBuilder
806
     */
807 32
    protected function createIndexModelBuilder(iterable $columns = null): ModelQueryBuilder
808
    {
809 32
        $builder = $this->createBuilder($this->getModelClass());
810
811
        $this
812 32
            ->applyColumnMapper($builder);
813
814
        $builder
815 32
            ->selectModelColumns($columns)
816 32
            ->fromModelTable();
817
818
        $this
819 32
            ->applyAliasFilters($builder)
820 32
            ->applySorts($builder)
821 32
            ->applyRelationshipFiltersAndSorts($builder)
822 32
            ->applyPaging($builder);
823
824 32
        $result = $this->builderOnIndex($builder);
825
826 32
        $this->clearBuilderParameters();
827
828 32
        return $result;
829
    }
830
831
    /**
832
     * @return ModelQueryBuilder
833
     */
834 5
    protected function createDeleteModelBuilder(): ModelQueryBuilder
835
    {
836
        $builder = $this
837 5
            ->createBuilder($this->getModelClass())
838 5
            ->deleteModels();
839
840 5
        $this->applyTableFilters($builder);
841
842 5
        $result = $this->builderOnDelete($builder);
843
844 5
        $this->clearBuilderParameters();
845
846 5
        return $result;
847
    }
848
849
    /**
850
     * @inheritdoc
851
     */
852 16
    public function index(): PaginatedDataInterface
853
    {
854 16
        $builder = $this->createIndexModelBuilder();
855 16
        $data    = $this->fetchResources($builder, $builder->getModelClass());
856
857 16
        return $data;
858
    }
859
860
    /**
861
     * @inheritdoc
862
     */
863 2
    public function indexIdentities(): array
864
    {
865 2
        $pkName  = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
866 2
        $builder = $this->createIndexModelBuilder([$pkName]);
0 ignored issues
show
Documentation introduced by
array($pkName) is of type array<integer,string,{"0":"string"}>, but the function expects a object<Limoncello\Flute\Api\iterable>|null.

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...
867
        /** @var Generator $data */
868 2
        $data   = $this->fetchColumn($builder, $builder->getModelClass(), $pkName);
869 2
        $result = iterator_to_array($data);
870
871 2
        return $result;
872
    }
873
874
    /**
875
     * @inheritdoc
876
     */
877 14
    public function read($index)
878
    {
879 14
        $this->withIndexFilter($index);
880
881 12
        $builder = $this->createIndexModelBuilder();
882 12
        $data    = $this->fetchResource($builder, $builder->getModelClass());
883
884 12
        return $data;
885
    }
886
887
    /**
888
     * @inheritdoc
889
     */
890 1
    public function count(): ?int
891
    {
892
        $builder = $this
893 1
            ->createBuilder($this->getModelClass())
894 1
            ->select('COUNT(*)')
895 1
            ->fromModelTable();
896
897 1
        $this->applyAliasFilters($builder);
898
899 1
        $this->clearBuilderParameters()->clearFetchParameters();
900
901 1
        $result = $this->builderOnCount($builder)->execute()->fetchColumn();
902
903 1
        return $result === false ? null : $result;
904
    }
905
906
    /**
907
     * @param string        $relationshipName
908
     * @param iterable|null $relationshipFilters
909
     * @param iterable|null $relationshipSorts
910
     * @param iterable|null $columns
911
     *
912
     * @return ModelQueryBuilder
913
     */
914 10
    public function createReadRelationshipBuilder(
915
        string $relationshipName,
916
        iterable $relationshipFilters = null,
917
        iterable $relationshipSorts = null,
918
        iterable $columns = null
919
    ): ModelQueryBuilder {
920
        // as we read data from a relationship our main table and model would be the table/model in the relationship
921
        // so 'root' model(s) will be located in the reverse relationship.
922
923
        list ($targetModelClass, $reverseRelName) =
924 10
            $this->getModelSchemes()->getReverseRelationship($this->getModelClass(), $relationshipName);
925
926
        $builder = $this
927 10
            ->createBuilder($targetModelClass)
928 10
            ->selectModelColumns($columns)
929 10
            ->fromModelTable();
930
931
        // 'root' filters would be applied to the data in the reverse relationship ...
932 10
        if ($this->hasFilters() === true) {
933 10
            $filters = $this->getFilters();
934 10
            $sorts   = $this->getSorts();
935 10
            $this->areFiltersWithAnd() ?
936 9
                $builder->addRelationshipFiltersAndSortsWithAnd($reverseRelName, $filters, $sorts) :
0 ignored issues
show
Bug introduced by
It seems like $filters defined by $this->getFilters() on line 933 can also be of type null; however, Limoncello\Flute\Adapter...iltersAndSortsWithAnd() does only seem to accept object<Limoncello\Flute\Adapters\iterable>, 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...
937 1
                $builder->addRelationshipFiltersAndSortsWithOr($reverseRelName, $filters, $sorts);
0 ignored issues
show
Bug introduced by
It seems like $filters defined by $this->getFilters() on line 933 can also be of type null; however, Limoncello\Flute\Adapter...FiltersAndSortsWithOr() does only seem to accept object<Limoncello\Flute\Adapters\iterable>, 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...
938
        }
939
        // ... and the input filters to actual data we select
940 10
        if ($relationshipFilters !== null) {
941 7
            $builder->addFiltersWithAndToAlias($relationshipFilters);
942
        }
943 10
        if ($relationshipSorts !== null) {
944 3
            $builder->addSorts($relationshipSorts);
945
        }
946
947 10
        $this->applyPaging($builder);
948
949
        // While joining tables we select distinct rows.
950 10
        $builder->distinct();
951
952 10
        return $this->builderOnReadRelationship($builder);
953
    }
954
955
    /**
956
     * @inheritdoc
957
     */
958 9
    public function indexRelationship(
959
        string $name,
960
        iterable $relationshipFilters = null,
961
        iterable $relationshipSorts = null
962
    ) {
963
        // depending on the relationship type we expect the result to be either single resource or a collection
964 9
        $relationshipType = $this->getModelSchemes()->getRelationshipType($this->getModelClass(), $name);
965 9
        $isExpectMany     = $relationshipType === RelationshipTypes::HAS_MANY ||
966 9
            $relationshipType === RelationshipTypes::BELONGS_TO_MANY;
967
968 9
        $builder = $this->createReadRelationshipBuilder($name, $relationshipFilters, $relationshipSorts);
969
970 9
        $modelClass = $builder->getModelClass();
971 9
        $data       = $isExpectMany === true ?
972 9
            $this->fetchResources($builder, $modelClass) : $this->fetchResource($builder, $modelClass);
973
974 9
        return $data;
975
    }
976
977
    /**
978
     * @inheritdoc
979
     */
980 2
    public function indexRelationshipIdentities(
981
        string $name,
982
        iterable $relationshipFilters = null,
983
        iterable $relationshipSorts = null
984
    ): array {
985
        // depending on the relationship type we expect the result to be either single resource or a collection
986 2
        $relationshipType = $this->getModelSchemes()->getRelationshipType($this->getModelClass(), $name);
987 2
        $isExpectMany     = $relationshipType === RelationshipTypes::HAS_MANY ||
988 2
            $relationshipType === RelationshipTypes::BELONGS_TO_MANY;
989 2
        if ($isExpectMany === false) {
990 1
            throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT));
991
        }
992
993 1
        list ($targetModelClass) = $this->getModelSchemes()->getReverseRelationship($this->getModelClass(), $name);
994 1
        $targetPk = $this->getModelSchemes()->getPrimaryKey($targetModelClass);
995
996 1
        $builder = $this->createReadRelationshipBuilder($name, $relationshipFilters, $relationshipSorts, [$targetPk]);
0 ignored issues
show
Documentation introduced by
array($targetPk) is of type array<integer,string,{"0":"string"}>, but the function expects a object<Limoncello\Flute\Api\iterable>|null.

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...
997
998 1
        $modelClass = $builder->getModelClass();
999
        /** @var Generator $data */
1000 1
        $data   = $this->fetchColumn($builder, $modelClass, $targetPk);
1001 1
        $result = iterator_to_array($data);
1002
1003 1
        return $result;
1004
    }
1005
1006
    /**
1007
     * @inheritdoc
1008
     */
1009 3
    public function readRelationship(
1010
        $index,
1011
        string $name,
1012
        iterable $relationshipFilters = null,
1013
        iterable $relationshipSorts = null
1014
    ) {
1015 3
        return $this->withIndexFilter($index)->indexRelationship($name, $relationshipFilters, $relationshipSorts);
1016
    }
1017
1018
    /**
1019
     * @inheritdoc
1020
     */
1021 6
    public function hasInRelationship($parentId, string $name, $childId): bool
1022
    {
1023 6
        if ($parentId !== null && is_scalar($parentId) === false) {
1024 1
            throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT));
1025
        }
1026 5
        if ($childId !== null && is_scalar($childId) === false) {
1027 1
            throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT));
1028
        }
1029
1030 4
        $parentPkName  = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
1031 4
        $parentFilters = [$parentPkName => [FilterParameterInterface::OPERATION_EQUALS => [$parentId]]];
1032 4
        list($childClass) = $this->getModelSchemes()->getReverseRelationship($this->getModelClass(), $name);
1033 4
        $childPkName  = $this->getModelSchemes()->getPrimaryKey($childClass);
1034 4
        $childFilters = [$childPkName => [FilterParameterInterface::OPERATION_EQUALS => [$childId]]];
1035
1036
        $data = $this
1037 4
            ->clearBuilderParameters()
1038 4
            ->clearFetchParameters()
1039 4
            ->withFilters($parentFilters)
0 ignored issues
show
Documentation introduced by
$parentFilters is of type array<string,array<strin...ble|string|boolean"}>>>, but the function expects a object<Limoncello\Flute\Contracts\Api\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...
1040 4
            ->indexRelationship($name, $childFilters);
0 ignored issues
show
Documentation introduced by
$childFilters is of type array<string,array<strin...ble|string|boolean"}>>>, but the function expects a object<Limoncello\Flute\...acts\Api\iterable>|null.

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...
1041
1042 4
        $has = empty($data->getData()) === false;
1043
1044 4
        return $has;
1045
    }
1046
1047
    /**
1048
     * @inheritdoc
1049
     */
1050 1
    public function delete(): int
1051
    {
1052 1
        $deleted = $this->createDeleteBuilder()->execute();
1053
1054 1
        $this->clearFetchParameters();
1055
1056 1
        return (int)$deleted;
1057
    }
1058
1059
    /**
1060
     * @inheritdoc
1061
     */
1062 6
    public function remove($index): bool
1063
    {
1064 6
        $this->withIndexFilter($index);
1065
1066 5
        $deleted = $this->createDeleteBuilder()->execute();
1067
1068 4
        $this->clearFetchParameters();
1069
1070 4
        return (int)$deleted > 0;
1071
    }
1072
1073
    /**
1074
     * @inheritdoc
1075
     */
1076 5
    public function create($index, iterable $attributes, iterable $toMany): string
1077
    {
1078 5
        if ($index !== null && is_int($index) === false && is_string($index) === false) {
1079 1
            throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT));
1080
        }
1081
1082 4
        $allowedChanges = $this->filterAttributesOnCreate($index, $attributes);
1083
        $saveMain       = $this
1084 4
            ->createBuilder($this->getModelClass())
1085 4
            ->createModel($allowedChanges);
0 ignored issues
show
Documentation introduced by
$allowedChanges is of type object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\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...
1086 4
        $saveMain       = $this->builderSaveResourceOnCreate($saveMain);
1087 4
        $saveMain->getSQL(); // prepare
1088
1089 4
        $this->clearBuilderParameters()->clearFetchParameters();
1090
1091 4
        $this->inTransaction(function () use ($saveMain, $toMany, &$index) {
1092 4
            $saveMain->execute();
1093
1094
            // if no index given will use last insert ID as index
1095 4
            $index !== null ?: $index = $saveMain->getConnection()->lastInsertId();
1096
1097 4
            $inserted = 0;
1098 4
            foreach ($toMany as $relationshipName => $secondaryIds) {
1099 2
                $secondaryIdBindName = ':secondaryId';
1100 2
                $saveToMany          = $this->builderSaveRelationshipOnCreate(
1101 2
                    $relationshipName,
1102
                    $this
1103 2
                        ->createBuilderFromConnection($saveMain->getConnection(), $this->getModelClass())
1104 2
                        ->prepareCreateInToManyRelationship($relationshipName, $index, $secondaryIdBindName)
1105
                );
1106 2
                foreach ($secondaryIds as $secondaryId) {
1107 2
                    $inserted += (int)$saveToMany->setParameter($secondaryIdBindName, $secondaryId)->execute();
1108
                }
1109
            }
1110 4
        });
1111
1112 4
        return $index;
1113
    }
1114
1115
    /**
1116
     * @inheritdoc
1117
     */
1118 5
    public function update($index, iterable $attributes, iterable $toMany): int
1119
    {
1120 5
        if (is_int($index) === false && is_string($index) === false) {
1121 1
            throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT));
1122
        }
1123
1124 4
        $updated        = 0;
1125 4
        $pkName         = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
1126
        $filters        = [
1127
            $pkName => [
1128 4
                FilterParameterInterface::OPERATION_EQUALS => [$index],
1129
            ],
1130
        ];
1131 4
        $allowedChanges = $this->filterAttributesOnUpdate($attributes);
1132
        $saveMain       = $this
1133 4
            ->createBuilder($this->getModelClass())
1134 4
            ->updateModels($allowedChanges)
0 ignored issues
show
Documentation introduced by
$allowedChanges is of type object<Generator>, but the function expects a object<Limoncello\Flute\Adapters\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...
1135 4
            ->addFiltersWithAndToTable($filters);
0 ignored issues
show
Documentation introduced by
$filters is of type array<string,array<strin...0":"integer|string"}>>>, but the function expects a object<Limoncello\Flute\Adapters\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...
1136 4
        $saveMain       = $this->builderSaveResourceOnUpdate($saveMain);
1137 4
        $saveMain->getSQL(); // prepare
1138
1139 4
        $this->clearBuilderParameters()->clearFetchParameters();
1140
1141 4
        $this->inTransaction(function () use ($saveMain, $toMany, $index, &$updated) {
1142 4
            $updated = $saveMain->execute();
1143
1144 4
            foreach ($toMany as $relationshipName => $secondaryIds) {
1145 2
                $cleanToMany = $this->builderCleanRelationshipOnUpdate(
1146 2
                    $relationshipName,
1147
                    $this
1148 2
                        ->createBuilderFromConnection($saveMain->getConnection(), $this->getModelClass())
1149 2
                        ->clearToManyRelationship($relationshipName, $index)
1150
                );
1151 2
                $cleanToMany->execute();
1152
1153 2
                $secondaryIdBindName = ':secondaryId';
1154 2
                $saveToMany          = $this->builderSaveRelationshipOnUpdate(
1155 2
                    $relationshipName,
1156
                    $this
1157 2
                        ->createBuilderFromConnection($saveMain->getConnection(), $this->getModelClass())
1158 2
                        ->prepareCreateInToManyRelationship($relationshipName, $index, $secondaryIdBindName)
1159
                );
1160 2
                foreach ($secondaryIds as $secondaryId) {
1161 2
                    $updated += (int)$saveToMany->setParameter($secondaryIdBindName, $secondaryId)->execute();
1162
                }
1163
            }
1164 4
        });
1165
1166 4
        return (int)$updated;
1167
    }
1168
1169
    /**
1170
     * @return FactoryInterface
1171
     */
1172 45
    protected function getFactory(): FactoryInterface
1173
    {
1174 45
        return $this->factory;
1175
    }
1176
1177
    /**
1178
     * @return string
1179
     */
1180 46
    protected function getModelClass(): string
1181
    {
1182 46
        return $this->modelClass;
1183
    }
1184
1185
    /**
1186
     * @return ModelSchemeInfoInterface
1187
     */
1188 46
    protected function getModelSchemes(): ModelSchemeInfoInterface
1189
    {
1190 46
        return $this->modelSchemes;
1191
    }
1192
1193
    /**
1194
     * @return PaginationStrategyInterface
1195
     */
1196 7
    protected function getPaginationStrategy(): PaginationStrategyInterface
1197
    {
1198 7
        return $this->paginationStrategy;
1199
    }
1200
1201
    /**
1202
     * @param Closure $closure
1203
     *
1204
     * @return void
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use NoType.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1205
     */
1206 8
    public function inTransaction(Closure $closure): void
1207
    {
1208 8
        $connection = $this->getConnection();
1209 8
        $connection->beginTransaction();
1210
        try {
1211 8
            $isOk = ($closure() === false ? null : true);
1212 8
        } finally {
1213 8
            isset($isOk) === true ? $connection->commit() : $connection->rollBack();
1214
        }
1215
    }
1216
1217
    /**
1218
     * @inheritdoc
1219
     */
1220 24
    public function fetchResources(QueryBuilder $builder, string $modelClass): PaginatedDataInterface
1221
    {
1222 24
        $data = $this->fetchPaginatedResourcesWithoutRelationships($builder, $modelClass);
1223
1224 24
        if ($this->hasIncludes() === true) {
1225 14
            $this->loadRelationships($data);
1226 14
            $this->clearFetchParameters();
1227
        }
1228
1229 24
        return $data;
1230
    }
1231
1232
    /**
1233
     * @inheritdoc
1234
     */
1235 13
    public function fetchResource(QueryBuilder $builder, string $modelClass)
1236
    {
1237 13
        $data = $this->fetchResourceWithoutRelationships($builder, $modelClass);
1238
1239 13
        if ($this->hasIncludes() === true) {
1240 7
            $this->loadRelationships($data);
1241 7
            $this->clearFetchParameters();
1242
        }
1243
1244 13
        return $data;
1245
    }
1246
1247
    /**
1248
     * @inheritdoc
1249
     */
1250 2
    public function fetchRow(QueryBuilder $builder, string $modelClass): ?array
1251
    {
1252 2
        $model = null;
1253
1254 2
        $statement = $builder->execute();
1255 2
        $statement->setFetchMode(PDOConnection::FETCH_ASSOC);
1256
1257 2
        if (($attributes = $statement->fetch()) !== false) {
1258 2
            if ($this->isFetchTyped() === true) {
1259 1
                $platform  = $builder->getConnection()->getDatabasePlatform();
1260 1
                $typeNames = $this->getModelSchemes()->getAttributeTypes($modelClass);
1261 1
                $model     = $this->readRowFromAssoc($attributes, $typeNames, $platform);
1262
            } else {
1263 1
                $model = $attributes;
1264
            }
1265
        }
1266
1267 2
        $this->clearFetchParameters();
1268
1269 2
        return $model;
1270
    }
1271
1272
    /**
1273
     * @inheritdoc
1274
     */
1275 3
    public function fetchColumn(QueryBuilder $builder, string $modelClass, string $columnName): iterable
1276
    {
1277 3
        $statement = $builder->execute();
1278 3
        $statement->setFetchMode(PDOConnection::FETCH_ASSOC);
1279
1280 3
        if ($this->isFetchTyped() === true) {
1281 2
            $platform = $builder->getConnection()->getDatabasePlatform();
1282 2
            $typeName = $this->getModelSchemes()->getAttributeTypes($modelClass)[$columnName];
1283 2
            $type     = Type::getType($typeName);
1284 2
            while (($attributes = $statement->fetch()) !== false) {
1285 2
                $value     = $attributes[$columnName];
1286 2
                $converted = $type->convertToPHPValue($value, $platform);
1287
1288 2
                yield $converted;
1289
            }
1290
        } else {
1291 1
            while (($attributes = $statement->fetch()) !== false) {
1292 1
                $value = $attributes[$columnName];
1293
1294 1
                yield $value;
1295
            }
1296
        }
1297
1298 3
        $this->clearFetchParameters();
1299
    }
1300
1301
    /**
1302
     * @param QueryBuilder $builder
1303
     * @param string       $modelClass
1304
     *
1305
     * @return mixed|null
1306
     */
1307 13
    private function fetchResourceWithoutRelationships(QueryBuilder $builder, string $modelClass)
1308
    {
1309 13
        $model     = null;
1310 13
        $statement = $builder->execute();
1311
1312 13
        if ($this->isFetchTyped() === true) {
1313 11
            $statement->setFetchMode(PDOConnection::FETCH_ASSOC);
1314 11
            if (($attributes = $statement->fetch()) !== false) {
1315 11
                $platform  = $builder->getConnection()->getDatabasePlatform();
1316 11
                $typeNames = $this->getModelSchemes()->getAttributeTypes($modelClass);
1317 11
                $model     = $this->readResourceFromAssoc($modelClass, $attributes, $typeNames, $platform);
1318
            }
1319
        } else {
1320 2
            $statement->setFetchMode(PDOConnection::FETCH_CLASS, $modelClass);
1321 2
            if (($fetched = $statement->fetch()) !== false) {
1322 2
                $model = $fetched;
1323
            }
1324
        }
1325
1326 13
        return $model;
1327
    }
1328
1329
    /**
1330
     * @param QueryBuilder $builder
1331
     * @param string       $modelClass
1332
     * @param string       $keyColumnName
1333
     *
1334
     * @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...
1335
     */
1336 8
    private function fetchResourcesWithoutRelationships(
1337
        QueryBuilder $builder,
1338
        string $modelClass,
1339
        string $keyColumnName
1340
    ): iterable {
1341 8
        $statement = $builder->execute();
1342
1343 8
        if ($this->isFetchTyped() === true) {
1344 7
            $statement->setFetchMode(PDOConnection::FETCH_ASSOC);
1345 7
            $platform  = $builder->getConnection()->getDatabasePlatform();
1346 7
            $typeNames = $this->getModelSchemes()->getAttributeTypes($modelClass);
1347 7
            while (($attributes = $statement->fetch()) !== false) {
1348 6
                $model = $this->readResourceFromAssoc($modelClass, $attributes, $typeNames, $platform);
1349 6
                yield $model->{$keyColumnName} => $model;
1350
            }
1351
        } else {
1352 1
            $statement->setFetchMode(PDOConnection::FETCH_CLASS, $modelClass);
1353 1
            while (($model = $statement->fetch()) !== false) {
1354 1
                yield $model->{$keyColumnName} => $model;
1355
            }
1356
        }
1357
    }
1358
1359
    /**
1360
     * @param QueryBuilder $builder
1361
     * @param string       $modelClass
1362
     *
1363
     * @return PaginatedDataInterface
1364
     */
1365 28
    private function fetchPaginatedResourcesWithoutRelationships(
1366
        QueryBuilder $builder,
1367
        string $modelClass
1368
    ): PaginatedDataInterface {
1369 28
        list($models, $hasMore, $limit, $offset) = $this->fetchResourceCollection($builder, $modelClass);
1370
1371 28
        $data = $this->getFactory()
1372 28
            ->createPaginatedData($models)
1373 28
            ->markAsCollection()
1374 28
            ->setOffset($offset)
1375 28
            ->setLimit($limit);
1376
1377 28
        $hasMore === true ? $data->markHasMoreItems() : $data->markHasNoMoreItems();
1378
1379 28
        return $data;
1380
    }
1381
1382
    /**
1383
     * @param QueryBuilder $builder
1384
     * @param string       $modelClass
1385
     *
1386
     * @return array
1387
     */
1388 28
    private function fetchResourceCollection(QueryBuilder $builder, string $modelClass): array
1389
    {
1390 28
        $statement = $builder->execute();
1391
1392 28
        $models           = [];
1393 28
        $counter          = 0;
1394 28
        $hasMoreThanLimit = false;
1395 28
        $limit            = $builder->getMaxResults() !== null ? $builder->getMaxResults() - 1 : null;
1396
1397 28
        if ($this->isFetchTyped() === true) {
1398 27
            $platform  = $builder->getConnection()->getDatabasePlatform();
1399 27
            $typeNames = $this->getModelSchemes()->getAttributeTypes($modelClass);
1400 27
            $statement->setFetchMode(PDOConnection::FETCH_ASSOC);
1401 27
            while (($attributes = $statement->fetch()) !== false) {
1402 26
                $counter++;
1403 26
                if ($limit !== null && $counter > $limit) {
1404 6
                    $hasMoreThanLimit = true;
1405 6
                    break;
1406
                }
1407 26
                $models[] = $this->readResourceFromAssoc($modelClass, $attributes, $typeNames, $platform);
1408
            }
1409
        } else {
1410 1
            $statement->setFetchMode(PDOConnection::FETCH_CLASS, $modelClass);
1411 1
            while (($fetched = $statement->fetch()) !== false) {
1412 1
                $counter++;
1413 1
                if ($limit !== null && $counter > $limit) {
1414 1
                    $hasMoreThanLimit = true;
1415 1
                    break;
1416
                }
1417 1
                $models[] = $fetched;
1418
            }
1419
        }
1420
1421 28
        return [$models, $hasMoreThanLimit, $limit, $builder->getFirstResult()];
1422
    }
1423
1424
    /**
1425
     * @param null|string $index
1426
     * @param iterable    $attributes
1427
     *
1428
     * @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...
1429
     */
1430 4
    protected function filterAttributesOnCreate(?string $index, iterable $attributes): iterable
1431
    {
1432 4
        if ($index !== null) {
1433 1
            $pkName = $this->getModelSchemes()->getPrimaryKey($this->getModelClass());
1434 1
            yield $pkName => $index;
1435
        }
1436
1437 4
        $knownAttrAndTypes = $this->getModelSchemes()->getAttributeTypes($this->getModelClass());
1438 4
        foreach ($attributes as $attribute => $value) {
1439 4
            if (array_key_exists($attribute, $knownAttrAndTypes) === true) {
1440 4
                yield $attribute => $value;
1441
            }
1442
        }
1443
    }
1444
1445
    /**
1446
     * @param iterable $attributes
1447
     *
1448
     * @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...
1449
     */
1450 4
    protected function filterAttributesOnUpdate(iterable $attributes): iterable
1451
    {
1452 4
        $knownAttrAndTypes = $this->getModelSchemes()->getAttributeTypes($this->getModelClass());
1453 4
        foreach ($attributes as $attribute => $value) {
1454 4
            if (array_key_exists($attribute, $knownAttrAndTypes) === true) {
1455 4
                yield $attribute => $value;
1456
            }
1457
        }
1458
    }
1459
1460
    /**
1461
     * @param TagStorageInterface   $modelsAtPath
1462
     * @param ArrayObject           $classAtPath
1463
     * @param ArrayObject           $idsAtPath
1464
     * @param ModelStorageInterface $deDup
1465
     * @param string                $parentsPath
1466
     * @param array                 $childRelationships
1467
     *
1468
     * @return void
1469
     *
1470
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
1471
     */
1472 9
    private function loadRelationshipsLayer(
1473
        TagStorageInterface $modelsAtPath,
1474
        ArrayObject $classAtPath,
1475
        ArrayObject $idsAtPath,
1476
        ModelStorageInterface $deDup,
1477
        string $parentsPath,
1478
        array $childRelationships
1479
    ): void {
1480 9
        $rootClass   = $classAtPath[static::ROOT_PATH];
1481 9
        $parentClass = $classAtPath[$parentsPath];
1482 9
        $parents     = $modelsAtPath->get($parentsPath);
1483
1484
        // What should we do? We have do find all child resources for $parents at paths $childRelationships (1 level
1485
        // child paths) and add them to $relationships. While doing it we have to deduplicate resources with
1486
        // $models.
1487
1488 9
        $pkName = $this->getModelSchemes()->getPrimaryKey($parentClass);
1489
1490 9
        $registerModelAtPath = function ($model, string $path) use ($deDup, $modelsAtPath, $idsAtPath) {
1491 8
            return self::registerModelAtPath(
1492 8
                $model,
1493 8
                $path,
1494 8
                $this->getModelSchemes(),
1495 8
                $deDup,
1496 8
                $modelsAtPath,
1497 8
                $idsAtPath
1498
            );
1499 9
        };
1500
1501 9
        foreach ($childRelationships as $name) {
1502 9
            $childrenPath = $parentsPath !== static::ROOT_PATH ? $parentsPath . static::PATH_SEPARATOR . $name : $name;
1503
1504 9
            $relationshipType = $this->getModelSchemes()->getRelationshipType($parentClass, $name);
1505
            list ($targetModelClass, $reverseRelName) =
1506 9
                $this->getModelSchemes()->getReverseRelationship($parentClass, $name);
1507
1508
            $builder = $this
1509 9
                ->createBuilder($targetModelClass)
1510 9
                ->selectModelColumns()
1511 9
                ->fromModelTable();
1512
1513 9
            $classAtPath[$childrenPath] = $targetModelClass;
1514
1515
            switch ($relationshipType) {
1516 9
                case RelationshipTypes::BELONGS_TO:
1517
                    // for 'belongsTo' relationship all resources could be read at once.
1518 8
                    $parentIds            = $idsAtPath[$parentsPath];
1519 8
                    $clonedBuilder        = (clone $builder)->addRelationshipFiltersAndSortsWithAnd(
1520 8
                        $reverseRelName,
1521 8
                        [$pkName => [FilterParameterInterface::OPERATION_IN => $parentIds]],
0 ignored issues
show
Documentation introduced by
array($pkName => array(\...TION_IN => $parentIds)) is of type array<string,array>, but the function expects a object<Limoncello\Flute\Adapters\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...
1522 8
                        null
1523
                    );
1524 8
                    $unregisteredChildren = $this->fetchResourcesWithoutRelationships(
1525 8
                        $clonedBuilder,
1526 8
                        $clonedBuilder->getModelClass(),
1527 8
                        $this->getModelSchemes()->getPrimaryKey($clonedBuilder->getModelClass())
1528
                    );
1529 8
                    $children             = [];
1530 8
                    foreach ($unregisteredChildren as $index => $unregisteredChild) {
1531 7
                        $children[$index] = $registerModelAtPath($unregisteredChild, $childrenPath);
1532
                    }
1533 8
                    $fkNameToChild = $this->getModelSchemes()->getForeignKey($parentClass, $name);
1534 8
                    foreach ($parents as $parent) {
1535 8
                        $fkToChild       = $parent->{$fkNameToChild};
1536 8
                        $parent->{$name} = $children[$fkToChild] ?? null;
1537
                    }
1538 8
                    break;
1539 7
                case RelationshipTypes::HAS_MANY:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1540 5
                case RelationshipTypes::BELONGS_TO_MANY:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1541
                    // unfortunately we have paging limits for 'many' relationship thus we have read such
1542
                    // relationships for each 'parent' individually
1543 7
                    list ($queryOffset, $queryLimit) = $this->getPaginationStrategy()
1544 7
                        ->getParameters($rootClass, $parentClass, $parentsPath, $name);
1545 7
                    $builder->setFirstResult($queryOffset)->setMaxResults($queryLimit + 1);
1546 7
                    foreach ($parents as $parent) {
1547 7
                        $clonedBuilder = (clone $builder)->addRelationshipFiltersAndSortsWithAnd(
1548 7
                            $reverseRelName,
1549 7
                            [$pkName => [FilterParameterInterface::OPERATION_EQUALS => [$parent->{$pkName}]]],
0 ignored issues
show
Documentation introduced by
array($pkName => array(\...y($parent->{$pkName}))) is of type array<string,array<strin...<integer,?,{"0":"?"}>>>, but the function expects a object<Limoncello\Flute\Adapters\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...
1550 7
                            []
0 ignored issues
show
Documentation introduced by
array() is of type array, but the function expects a object<Limoncello\Flute\Adapters\iterable>|null.

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...
1551
                        );
1552 7
                        $children      = $this->fetchPaginatedResourcesWithoutRelationships(
1553 7
                            $clonedBuilder,
1554 7
                            $clonedBuilder->getModelClass()
1555
                        );
1556
1557 7
                        $deDupedChildren = [];
1558 7
                        foreach ($children->getData() as $child) {
1559 7
                            $deDupedChildren[] = $registerModelAtPath($child, $childrenPath);
1560
                        }
1561
1562 7
                        $paginated = $this->getFactory()
1563 7
                            ->createPaginatedData($deDupedChildren)
1564 7
                            ->markAsCollection()
1565 7
                            ->setOffset($children->getOffset())
1566 7
                            ->setLimit($children->getLimit());
1567 7
                        $children->hasMoreItems() === true ?
1568 7
                            $paginated->markHasMoreItems() : $paginated->markHasNoMoreItems();
1569
1570 7
                        $parent->{$name} = $paginated;
1571
                    }
1572 9
                    break;
1573
            }
1574
        }
1575
    }
1576
1577
    /**
1578
     * @param string $message
1579
     *
1580
     * @return string
1581
     */
1582 8
    private function getMessage(string $message): string
1583
    {
1584
        /** @var FormatterFactoryInterface $factory */
1585 8
        $factory   = $this->getContainer()->get(FormatterFactoryInterface::class);
1586 8
        $formatter = $factory->createFormatter(Messages::RESOURCES_NAMESPACE);
1587 8
        $result    = $formatter->formatMessage($message);
1588
1589 8
        return $result;
1590
    }
1591
1592
    /**
1593
     * @param string           $class
1594
     * @param array            $attributes
1595
     * @param Type[]           $typeNames
1596
     * @param AbstractPlatform $platform
1597
     *
1598
     * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use object.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1599
     *
1600
     * @SuppressWarnings(PHPMD.StaticAccess)
1601
     */
1602 33
    private function readResourceFromAssoc(
1603
        string $class,
1604
        array $attributes,
1605
        array $typeNames,
1606
        AbstractPlatform $platform
1607
    ) {
1608 33
        $instance = new $class();
1609 33
        foreach ($this->readTypedAttributes($attributes, $typeNames, $platform) as $name => $value) {
0 ignored issues
show
Documentation introduced by
$attributes is of type array, but the function expects a object<Limoncello\Flute\Api\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...
1610 33
            $instance->{$name} = $value;
1611
        }
1612
1613 33
        return $instance;
1614
    }
1615
1616
    /**
1617
     * @param array            $attributes
1618
     * @param Type[]           $typeNames
1619
     * @param AbstractPlatform $platform
1620
     *
1621
     * @return array
1622
     *
1623
     * @SuppressWarnings(PHPMD.StaticAccess)
1624
     */
1625 1
    private function readRowFromAssoc(array $attributes, array $typeNames, AbstractPlatform $platform): array
1626
    {
1627 1
        $row = [];
1628 1
        foreach ($this->readTypedAttributes($attributes, $typeNames, $platform) as $name => $value) {
0 ignored issues
show
Documentation introduced by
$attributes is of type array, but the function expects a object<Limoncello\Flute\Api\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...
1629 1
            $row[$name] = $value;
1630
        }
1631
1632 1
        return $row;
1633
    }
1634
1635
    /**
1636
     * @param iterable         $attributes
1637
     * @param array            $typeNames
1638
     * @param AbstractPlatform $platform
1639
     *
1640
     * @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...
1641
     */
1642 34
    private function readTypedAttributes(iterable $attributes, array $typeNames, AbstractPlatform $platform): iterable
1643
    {
1644 34
        foreach ($attributes as $name => $value) {
1645 34
            yield $name => (array_key_exists($name, $typeNames) === true ?
1646 34
                Type::getType($typeNames[$name])->convertToPHPValue($value, $platform) : $value);
1647
        }
1648
    }
1649
}
1650