Completed
Push — develop ( 90e0de...468005 )
by Neomerx
03:38
created

Crud::withoutPaging()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 0
crap 1
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\DBALException;
23
use Doctrine\DBAL\Driver\PDOConnection;
24
use Doctrine\DBAL\Platforms\AbstractPlatform;
25
use Doctrine\DBAL\Query\QueryBuilder;
26
use Doctrine\DBAL\Types\Type;
27
use Generator;
28
use Limoncello\Container\Traits\HasContainerTrait;
29
use Limoncello\Contracts\Data\ModelSchemaInfoInterface;
30
use Limoncello\Contracts\Data\RelationshipTypes;
31
use Limoncello\Contracts\L10n\FormatterFactoryInterface;
32
use Limoncello\Flute\Adapters\ModelQueryBuilder;
33
use Limoncello\Flute\Contracts\Api\CrudInterface;
34
use Limoncello\Flute\Contracts\Api\RelationshipPaginationStrategyInterface;
35
use Limoncello\Flute\Contracts\FactoryInterface;
36
use Limoncello\Flute\Contracts\Http\Query\FilterParameterInterface;
37
use Limoncello\Flute\Contracts\Models\ModelStorageInterface;
38
use Limoncello\Flute\Contracts\Models\PaginatedDataInterface;
39
use Limoncello\Flute\Contracts\Models\TagStorageInterface;
40
use Limoncello\Flute\Exceptions\InvalidArgumentException;
41
use Limoncello\Flute\L10n\Messages;
42
use Limoncello\Flute\Package\FluteSettings;
43
use Neomerx\JsonApi\Contracts\Document\DocumentInterface;
44
use Psr\Container\ContainerExceptionInterface;
45
use Psr\Container\ContainerInterface;
46
use Psr\Container\NotFoundExceptionInterface;
47
use Traversable;
48
49
/**
50
 * @package Limoncello\Flute
51
 *
52
 * @SuppressWarnings(PHPMD.TooManyMethods)
53
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
54
 * @SuppressWarnings(PHPMD.ExcessiveClassLength)
55
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
56
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
57
 */
58
class Crud implements CrudInterface
59
{
60
    use HasContainerTrait;
61
62
    /** Internal constant. Path constant. */
63
    protected const ROOT_PATH = '';
64
65
    /** Internal constant. Path constant. */
66
    protected const PATH_SEPARATOR = DocumentInterface::PATH_SEPARATOR;
67
68
    /**
69
     * @var FactoryInterface
70
     */
71
    private $factory;
72
73
    /**
74
     * @var string
75
     */
76
    private $modelClass;
77
78
    /**
79
     * @var ModelSchemaInfoInterface
80
     */
81
    private $modelSchemas;
82
83
    /**
84
     * @var RelationshipPaginationStrategyInterface
85
     */
86
    private $relPagingStrategy;
87
88
    /**
89
     * @var Connection
90
     */
91
    private $connection;
92
93
    /**
94
     * @var iterable|null
95
     */
96
    private $filterParameters = null;
97
98
    /**
99
     * @var bool
100
     */
101
    private $areFiltersWithAnd = true;
102
103
    /**
104
     * @var iterable|null
105
     */
106
    private $sortingParameters = null;
107
108
    /**
109
     * @var array
110
     */
111
    private $relFiltersAndSorts = [];
112
113
    /**
114
     * @var iterable|null
115
     */
116
    private $includePaths = null;
117
118
    /**
119
     * @var int|null
120
     */
121
    private $pagingOffset = null;
122
123
    /**
124
     * @var Closure|null
125
     */
126
    private $columnMapper = null;
127
128
    /**
129
     * @var bool
130
     */
131
    private $isFetchTyped;
132
133
    /**
134
     * @var int|null
135
     */
136
    private $pagingLimit = null;
137
138
    /** internal constant */
139
    private const REL_FILTERS_AND_SORTS__FILTERS = 0;
140
141
    /** internal constant */
142
    private const REL_FILTERS_AND_SORTS__SORTS = 1;
143
144
    /**
145
     * @param ContainerInterface $container
146
     * @param string             $modelClass
147
     *
148
     * @throws ContainerExceptionInterface
149
     * @throws NotFoundExceptionInterface
150
     */
151 60
    public function __construct(ContainerInterface $container, string $modelClass)
152
    {
153 60
        $this->setContainer($container);
154
155 60
        $this->modelClass        = $modelClass;
156 60
        $this->factory           = $this->getContainer()->get(FactoryInterface::class);
157 60
        $this->modelSchemas      = $this->getContainer()->get(ModelSchemaInfoInterface::class);
158 60
        $this->relPagingStrategy = $this->getContainer()->get(RelationshipPaginationStrategyInterface::class);
159 60
        $this->connection        = $this->getContainer()->get(Connection::class);
160
161 60
        $this->clearBuilderParameters()->clearFetchParameters();
162
    }
163
164
    /**
165
     * @param Closure $mapper
166
     *
167
     * @return self
168
     */
169 1
    public function withColumnMapper(Closure $mapper): self
170
    {
171 1
        $this->columnMapper = $mapper;
172
173 1
        return $this;
174
    }
175
176
    /**
177
     * @inheritdoc
178
     */
179 42
    public function withFilters(iterable $filterParameters): CrudInterface
180
    {
181 42
        $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...
182
183 42
        return $this;
184
    }
185
186
    /**
187
     * @inheritdoc
188
     */
189 23
    public function withIndexFilter($index): CrudInterface
190
    {
191 23
        if (is_int($index) === false && is_string($index) === false) {
192 3
            throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT));
193
        }
194
195 20
        $pkName = $this->getModelSchemas()->getPrimaryKey($this->getModelClass());
196 20
        $this->withFilters([
0 ignored issues
show
Documentation introduced by
array($pkName => array(\...UALS => array($index))) is of type array<?,array<string|int...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...
197
            $pkName => [
198 20
                FilterParameterInterface::OPERATION_EQUALS => [$index],
199
            ],
200
        ]);
201
202 20
        return $this;
203
    }
204
205
    /**
206
     * @inheritdoc
207
     */
208 7
    public function withRelationshipFilters(string $name, iterable $filters): CrudInterface
209
    {
210 7
        $this->relFiltersAndSorts[$name][self::REL_FILTERS_AND_SORTS__FILTERS] = $filters;
211
212 7
        return $this;
213
    }
214
215
    /**
216
     * @inheritdoc
217
     */
218 1
    public function withRelationshipSorts(string $name, iterable $sorts): CrudInterface
219
    {
220 1
        $this->relFiltersAndSorts[$name][self::REL_FILTERS_AND_SORTS__SORTS] = $sorts;
221
222 1
        return $this;
223
    }
224
225
    /**
226
     * @inheritdoc
227
     */
228 16
    public function combineWithAnd(): CrudInterface
229
    {
230 16
        $this->areFiltersWithAnd = true;
231
232 16
        return $this;
233
    }
234
235
    /**
236
     * @inheritdoc
237
     */
238 2
    public function combineWithOr(): CrudInterface
239
    {
240 2
        $this->areFiltersWithAnd = false;
241
242 2
        return $this;
243
    }
244
245
    /**
246
     * @return bool
247
     */
248 35
    private function hasColumnMapper(): bool
249
    {
250 35
        return $this->columnMapper !== null;
251
    }
252
253
    /**
254
     * @return Closure
255
     */
256 1
    private function getColumnMapper(): Closure
257
    {
258 1
        return $this->columnMapper;
259
    }
260
261
    /**
262
     * @return bool
263
     */
264 46
    private function hasFilters(): bool
265
    {
266 46
        return empty($this->filterParameters) === false;
267
    }
268
269
    /**
270
     * @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...
271
     */
272 35
    private function getFilters(): iterable
273
    {
274 35
        return $this->filterParameters;
275
    }
276
277
    /**
278
     * @return bool
279
     */
280 35
    private function areFiltersWithAnd(): bool
281
    {
282 35
        return $this->areFiltersWithAnd;
283
    }
284
285
    /**
286
     * @inheritdoc
287
     */
288 17
    public function withSorts(iterable $sortingParameters): CrudInterface
289
    {
290 17
        $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...
291
292 17
        return $this;
293
    }
294
295
    /**
296
     * @return bool
297
     */
298 35
    private function hasSorts(): bool
299
    {
300 35
        return empty($this->sortingParameters) === false;
301
    }
302
303
    /**
304
     * @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...
305
     */
306 14
    private function getSorts(): ?iterable
307
    {
308 14
        return $this->sortingParameters;
309
    }
310
311
    /**
312
     * @inheritdoc
313
     */
314 20
    public function withIncludes(iterable $includePaths): CrudInterface
315
    {
316 20
        $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...
317
318 20
        return $this;
319
    }
320
321
    /**
322
     * @return bool
323
     */
324 37
    private function hasIncludes(): bool
325
    {
326 37
        return empty($this->includePaths) === false;
327
    }
328
329
    /**
330
     * @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...
331
     */
332 20
    private function getIncludes(): iterable
333
    {
334 20
        return $this->includePaths;
335
    }
336
337
    /**
338
     * @inheritdoc
339
     */
340 20
    public function withPaging(int $offset, int $limit): CrudInterface
341
    {
342 20
        $this->pagingOffset = $offset;
343 20
        $this->pagingLimit  = $limit;
344
345 20
        return $this;
346
    }
347
348
    /**
349
     * @inheritdoc
350
     */
351 1
    public function withoutPaging(): CrudInterface
352
    {
353 1
        $this->pagingOffset = null;
354 1
        $this->pagingLimit  = null;
355
356 1
        return $this;
357
    }
358
359
    /**
360
     * @return self
361
     */
362 60
    public function shouldBeTyped(): self
363
    {
364 60
        $this->isFetchTyped = true;
365
366 60
        return $this;
367
    }
368
369
    /**
370
     * @return self
371
     */
372 4
    public function shouldBeUntyped(): self
373
    {
374 4
        $this->isFetchTyped = false;
375
376 4
        return $this;
377
    }
378
379
    /**
380
     * @return bool
381
     */
382 44
    private function hasPaging(): bool
383
    {
384 44
        return $this->pagingOffset !== null && $this->pagingLimit !== null;
385
    }
386
387
    /**
388
     * @return int
389
     */
390 18
    private function getPagingOffset(): int
391
    {
392 18
        return $this->pagingOffset;
393
    }
394
395
    /**
396
     * @return int
397
     */
398 18
    private function getPagingLimit(): int
399
    {
400 18
        return $this->pagingLimit;
401
    }
402
403
    /**
404
     * @return bool
405
     */
406 42
    private function isFetchTyped(): bool
407
    {
408 42
        return $this->isFetchTyped;
409
    }
410
411
    /**
412
     * @return Connection
413
     */
414 47
    protected function getConnection(): Connection
415
    {
416 47
        return $this->connection;
417
    }
418
419
    /**
420
     * @param string $modelClass
421
     *
422
     * @return ModelQueryBuilder
423
     */
424 47
    protected function createBuilder(string $modelClass): ModelQueryBuilder
425
    {
426 47
        return $this->createBuilderFromConnection($this->getConnection(), $modelClass);
427
    }
428
429
    /**
430
     * @param Connection $connection
431
     * @param string     $modelClass
432
     *
433
     * @return ModelQueryBuilder
434
     */
435 47
    private function createBuilderFromConnection(Connection $connection, string $modelClass): ModelQueryBuilder
436
    {
437 47
        return $this->getFactory()->createModelQueryBuilder($connection, $modelClass, $this->getModelSchemas());
438
    }
439
440
    /**
441
     * @param ModelQueryBuilder $builder
442
     *
443
     * @return Crud
444
     */
445 35
    protected function applyColumnMapper(ModelQueryBuilder $builder): self
446
    {
447 35
        if ($this->hasColumnMapper() === true) {
448 1
            $builder->setColumnToDatabaseMapper($this->getColumnMapper());
449
        }
450
451 35
        return $this;
452
    }
453
454
    /**
455
     * @param ModelQueryBuilder $builder
456
     *
457
     * @return Crud
458
     *
459
     * @throws DBALException
460
     */
461 35
    protected function applyAliasFilters(ModelQueryBuilder $builder): self
462
    {
463 35
        if ($this->hasFilters() === true) {
464 24
            $filters = $this->getFilters();
465 24
            $this->areFiltersWithAnd() === true ?
466 24
                $builder->addFiltersWithAndToAlias($filters) : $builder->addFiltersWithOrToAlias($filters);
0 ignored issues
show
Bug introduced by
It seems like $filters defined by $this->getFilters() on line 464 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 464 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...
467
        }
468
469 35
        return $this;
470
    }
471
472
    /**
473
     * @param ModelQueryBuilder $builder
474
     *
475
     * @return self
476
     *
477
     * @throws DBALException
478
     */
479 5
    protected function applyTableFilters(ModelQueryBuilder $builder): self
480
    {
481 5
        if ($this->hasFilters() === true) {
482 5
            $filters = $this->getFilters();
483 5
            $this->areFiltersWithAnd() === true ?
484 5
                $builder->addFiltersWithAndToTable($filters) : $builder->addFiltersWithOrToTable($filters);
0 ignored issues
show
Bug introduced by
It seems like $filters defined by $this->getFilters() on line 482 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 482 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...
485
        }
486
487 5
        return $this;
488
    }
489
490
    /**
491
     * @param ModelQueryBuilder $builder
492
     *
493
     * @return self
494
     *
495
     * @throws DBALException
496
     */
497 35
    protected function applyRelationshipFiltersAndSorts(ModelQueryBuilder $builder): self
498
    {
499
        // While joining tables we select distinct rows. This flag used to apply `distinct` no more than once.
500 35
        $distinctApplied = false;
501
502 35
        foreach ($this->relFiltersAndSorts as $relationshipName => $filtersAndSorts) {
503 7
            assert(is_string($relationshipName) === true && is_array($filtersAndSorts) === true);
504 7
            $builder->addRelationshipFiltersAndSortsWithAnd(
505 7
                $relationshipName,
506 7
                $filtersAndSorts[self::REL_FILTERS_AND_SORTS__FILTERS] ?? [],
507 7
                $filtersAndSorts[self::REL_FILTERS_AND_SORTS__SORTS] ?? []
508
            );
509
510 7
            if ($distinctApplied === false) {
511 7
                $builder->distinct();
512 7
                $distinctApplied = true;
513
            }
514
        }
515
516 35
        return $this;
517
    }
518
519
    /**
520
     * @param ModelQueryBuilder $builder
521
     *
522
     * @return self
523
     */
524 35
    protected function applySorts(ModelQueryBuilder $builder): self
525
    {
526 35
        if ($this->hasSorts() === true) {
527 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...
528
        }
529
530 35
        return $this;
531
    }
532
533
    /**
534
     * @param ModelQueryBuilder $builder
535
     *
536
     * @return self
537
     */
538 44
    protected function applyPaging(ModelQueryBuilder $builder): self
539
    {
540 44
        if ($this->hasPaging() === true) {
541 18
            $builder->setFirstResult($this->getPagingOffset());
542 18
            $builder->setMaxResults($this->getPagingLimit() + 1);
543
        }
544
545 44
        return $this;
546
    }
547
548
    /**
549
     * @return self
550
     */
551 60
    protected function clearBuilderParameters(): self
552
    {
553 60
        $this->columnMapper       = null;
554 60
        $this->filterParameters   = null;
555 60
        $this->areFiltersWithAnd  = true;
556 60
        $this->sortingParameters  = null;
557 60
        $this->pagingOffset       = null;
558 60
        $this->pagingLimit        = null;
559 60
        $this->relFiltersAndSorts = [];
560
561 60
        return $this;
562
    }
563
564
    /**
565
     * @return self
566
     */
567 60
    private function clearFetchParameters(): self
568
    {
569 60
        $this->includePaths = null;
570 60
        $this->shouldBeTyped();
571
572 60
        return $this;
573
    }
574
575
    /**
576
     * @param ModelQueryBuilder $builder
577
     *
578
     * @return ModelQueryBuilder
579
     */
580 2
    protected function builderOnCount(ModelQueryBuilder $builder): ModelQueryBuilder
581
    {
582 2
        return $builder;
583
    }
584
585
    /**
586
     * @param ModelQueryBuilder $builder
587
     *
588
     * @return ModelQueryBuilder
589
     */
590 35
    protected function builderOnIndex(ModelQueryBuilder $builder): ModelQueryBuilder
591
    {
592 35
        return $builder;
593
    }
594
595
    /**
596
     * @param ModelQueryBuilder $builder
597
     *
598
     * @return ModelQueryBuilder
599
     */
600 10
    protected function builderOnReadRelationship(ModelQueryBuilder $builder): ModelQueryBuilder
601
    {
602 10
        return $builder;
603
    }
604
605
    /**
606
     * @param ModelQueryBuilder $builder
607
     *
608
     * @return ModelQueryBuilder
609
     */
610 4
    protected function builderSaveResourceOnCreate(ModelQueryBuilder $builder): ModelQueryBuilder
611
    {
612 4
        return $builder;
613
    }
614
615
    /**
616
     * @param ModelQueryBuilder $builder
617
     *
618
     * @return ModelQueryBuilder
619
     */
620 4
    protected function builderSaveResourceOnUpdate(ModelQueryBuilder $builder): ModelQueryBuilder
621
    {
622 4
        return $builder;
623
    }
624
625
    /**
626
     * @param string            $relationshipName
627
     * @param ModelQueryBuilder $builder
628
     *
629
     * @return ModelQueryBuilder
630
     *
631
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
632
     */
633 2
    protected function builderSaveRelationshipOnCreate(/** @noinspection PhpUnusedParameterInspection */
634
        $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...
635
        ModelQueryBuilder $builder
636
    ): ModelQueryBuilder {
637 2
        return $builder;
638
    }
639
640
    /**
641
     * @param string            $relationshipName
642
     * @param ModelQueryBuilder $builder
643
     *
644
     * @return ModelQueryBuilder
645
     *
646
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
647
     */
648 2
    protected function builderSaveRelationshipOnUpdate(/** @noinspection PhpUnusedParameterInspection */
649
        $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...
650
        ModelQueryBuilder $builder
651
    ): ModelQueryBuilder {
652 2
        return $builder;
653
    }
654
655
    /**
656
     * @param string            $relationshipName
657
     * @param ModelQueryBuilder $builder
658
     *
659
     * @return ModelQueryBuilder
660
     *
661
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
662
     */
663 2
    protected function builderCleanRelationshipOnUpdate(/** @noinspection PhpUnusedParameterInspection */
664
        $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...
665
        ModelQueryBuilder $builder
666
    ): ModelQueryBuilder {
667 2
        return $builder;
668
    }
669
670
    /**
671
     * @param ModelQueryBuilder $builder
672
     *
673
     * @return ModelQueryBuilder
674
     */
675 5
    protected function builderOnDelete(ModelQueryBuilder $builder): ModelQueryBuilder
676
    {
677 5
        return $builder;
678
    }
679
680
    /**
681
     * @param PaginatedDataInterface|mixed|null $data
682
     *
683
     * @return void
684
     *
685
     * @SuppressWarnings(PHPMD.ElseExpression)
686
     *
687
     * @throws DBALException
688
     */
689 20
    private function loadRelationships($data): void
690
    {
691 20
        $isPaginated = $data instanceof PaginatedDataInterface;
692 20
        $hasData     = ($isPaginated === true && empty($data->getData()) === false) ||
693 20
            ($isPaginated === false && $data !== null);
694
695 20
        if ($hasData === true && $this->hasIncludes() === true) {
696 20
            $modelStorage = $this->getFactory()->createModelStorage($this->getModelSchemas());
697 20
            $modelsAtPath = $this->getFactory()->createTagStorage();
698
699
            // we gonna send these objects via function params so it is an equivalent for &array
700 20
            $classAtPath = new ArrayObject();
701 20
            $idsAtPath   = new ArrayObject();
702
703 20
            $registerModelAtRoot = function ($model) use ($modelStorage, $modelsAtPath, $idsAtPath): void {
704 20
                self::registerModelAtPath(
705 20
                    $model,
706 20
                    static::ROOT_PATH,
707 20
                    $this->getModelSchemas(),
708 20
                    $modelStorage,
709 20
                    $modelsAtPath,
710 20
                    $idsAtPath
711
                );
712 20
            };
713
714 20
            $model = null;
715 20
            if ($isPaginated === true) {
716 13
                foreach ($data->getData() as $model) {
717 13
                    $registerModelAtRoot($model);
718
                }
719
            } else {
720 7
                $model = $data;
721 7
                $registerModelAtRoot($model);
722
            }
723 20
            assert($model !== null);
724 20
            $classAtPath[static::ROOT_PATH] = get_class($model);
725
726 20
            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...
727 9
                $this->loadRelationshipsLayer(
728 9
                    $modelsAtPath,
729 9
                    $classAtPath,
730 9
                    $idsAtPath,
731 9
                    $modelStorage,
732 9
                    $parentPath,
733 9
                    $childPaths
734
                );
735
            }
736
        }
737
    }
738
739
    /**
740
     * A helper to remember all model related data. Helps to ensure we consistently handle models in CRUD.
741
     *
742
     * @param mixed                    $model
743
     * @param string                   $path
744
     * @param ModelSchemaInfoInterface $modelSchemas
745
     * @param ModelStorageInterface    $modelStorage
746
     * @param TagStorageInterface      $modelsAtPath
747
     * @param ArrayObject              $idsAtPath
748
     *
749
     * @return mixed
750
     */
751 20
    private static function registerModelAtPath(
752
        $model,
753
        string $path,
754
        ModelSchemaInfoInterface $modelSchemas,
755
        ModelStorageInterface $modelStorage,
756
        TagStorageInterface $modelsAtPath,
757
        ArrayObject $idsAtPath
758
    ) {
759 20
        $uniqueModel = $modelStorage->register($model);
760 20
        if ($uniqueModel !== null) {
761 20
            $modelsAtPath->register($uniqueModel, $path);
762 20
            $pkName             = $modelSchemas->getPrimaryKey(get_class($uniqueModel));
763 20
            $modelId            = $uniqueModel->{$pkName};
764 20
            $idsAtPath[$path][] = $modelId;
765
        }
766
767 20
        return $uniqueModel;
768
    }
769
770
    /**
771
     * @param iterable $paths (string[])
772
     *
773
     * @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...
774
     */
775 20
    private static function getPaths(iterable $paths): iterable
776
    {
777
        // The idea is to normalize paths. It means build all intermediate paths.
778
        // e.g. if only `a.b.c` path it given it will be normalized to `a`, `a.b` and `a.b.c`.
779
        // Path depths store depth of each path (e.g. 0 for root, 1 for `a`, 2 for `a.b` and etc).
780
        // It is needed for yielding them in correct order (from top level to bottom).
781 20
        $normalizedPaths = [];
782 20
        $pathsDepths     = [];
783 20
        foreach ($paths as $path) {
784 9
            assert(is_array($path) || $path instanceof Traversable);
785 9
            $parentDepth = 0;
786 9
            $tmpPath     = static::ROOT_PATH;
787 9
            foreach ($path as $pathPiece) {
788 9
                assert(is_string($pathPiece));
789 9
                $parent                    = $tmpPath;
790 9
                $tmpPath                   = empty($tmpPath) === true ?
791 9
                    $pathPiece : $tmpPath . static::PATH_SEPARATOR . $pathPiece;
792 9
                $normalizedPaths[$tmpPath] = [$parent, $pathPiece];
793 9
                $pathsDepths[$parent]      = $parentDepth++;
794
            }
795
        }
796
797
        // Here we collect paths in form of parent => [list of children]
798
        // 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...
799 20
        $parentWithChildren = [];
800 20
        foreach ($normalizedPaths as $path => list ($parent, $childPath)) {
801 9
            $parentWithChildren[$parent][] = $childPath;
802
        }
803
804
        // And finally sort by path depth and yield parent with its children. Top level paths first then deeper ones.
805 20
        asort($pathsDepths, SORT_NUMERIC);
806 20
        foreach ($pathsDepths as $parent => $depth) {
807 9
            assert($depth !== null); // suppress unused
808 9
            $childPaths = $parentWithChildren[$parent];
809 9
            yield [$parent, $childPaths];
810
        }
811
    }
812
813
    /**
814
     * @inheritdoc
815
     */
816 2
    public function createIndexBuilder(iterable $columns = null): QueryBuilder
817
    {
818 2
        return $this->createIndexModelBuilder($columns);
819
    }
820
821
    /**
822
     * @inheritdoc
823
     */
824 5
    public function createDeleteBuilder(): QueryBuilder
825
    {
826 5
        return $this->createDeleteModelBuilder();
827
    }
828
829
    /**
830
     * @param iterable|null $columns
831
     *
832
     * @return ModelQueryBuilder
833
     *
834
     * @throws DBALException
835
     */
836 35
    protected function createIndexModelBuilder(iterable $columns = null): ModelQueryBuilder
837
    {
838 35
        $builder = $this->createBuilder($this->getModelClass());
839
840
        $this
841 35
            ->applyColumnMapper($builder);
842
843
        $builder
844 35
            ->selectModelColumns($columns)
845 35
            ->fromModelTable();
846
847
        $this
848 35
            ->applyAliasFilters($builder)
849 35
            ->applySorts($builder)
850 35
            ->applyRelationshipFiltersAndSorts($builder)
851 35
            ->applyPaging($builder);
852
853 35
        $result = $this->builderOnIndex($builder);
854
855 35
        $this->clearBuilderParameters();
856
857 35
        return $result;
858
    }
859
860
    /**
861
     * @return ModelQueryBuilder
862
     *
863
     * @throws DBALException
864
     */
865 5
    protected function createDeleteModelBuilder(): ModelQueryBuilder
866
    {
867
        $builder = $this
868 5
            ->createBuilder($this->getModelClass())
869 5
            ->deleteModels();
870
871 5
        $this->applyTableFilters($builder);
872
873 5
        $result = $this->builderOnDelete($builder);
874
875 5
        $this->clearBuilderParameters();
876
877 5
        return $result;
878
    }
879
880
    /**
881
     * @inheritdoc
882
     */
883 17
    public function index(): PaginatedDataInterface
884
    {
885 17
        $builder = $this->createIndexModelBuilder();
886 17
        $data    = $this->fetchResources($builder, $builder->getModelClass());
887
888 17
        return $data;
889
    }
890
891
    /**
892
     * @inheritdoc
893
     */
894 2
    public function indexIdentities(): array
895
    {
896 2
        $pkName  = $this->getModelSchemas()->getPrimaryKey($this->getModelClass());
897 2
        $builder = $this->createIndexModelBuilder([$pkName]);
0 ignored issues
show
Documentation introduced by
array($pkName) is of type array<integer,?,{"0":"?"}>, 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...
898
        /** @var Generator $data */
899 2
        $data   = $this->fetchColumn($builder, $builder->getModelClass(), $pkName);
900 2
        $result = iterator_to_array($data);
901
902 2
        return $result;
903
    }
904
905
    /**
906
     * @inheritdoc
907
     */
908 14
    public function read($index)
909
    {
910 14
        $this->withIndexFilter($index);
911
912 12
        $builder = $this->createIndexModelBuilder();
913 12
        $data    = $this->fetchResource($builder, $builder->getModelClass());
914
915 12
        return $data;
916
    }
917
918
    /**
919
     * @inheritdoc
920
     */
921 2
    public function count(): ?int
922
    {
923 2
        $result = $this->builderOnCount(
924 2
            $this->createCountBuilderFromBuilder($this->createIndexModelBuilder())
925 2
        )->execute()->fetchColumn();
926
927 2
        return $result === false ? null : $result;
928
    }
929
930
    /**
931
     * @param string        $relationshipName
932
     * @param iterable|null $relationshipFilters
933
     * @param iterable|null $relationshipSorts
934
     * @param iterable|null $columns
935
     *
936
     * @return ModelQueryBuilder
937
     *
938
     * @throws DBALException
939
     */
940 10
    public function createReadRelationshipBuilder(
941
        string $relationshipName,
942
        iterable $relationshipFilters = null,
943
        iterable $relationshipSorts = null,
944
        iterable $columns = null
945
    ): ModelQueryBuilder {
946 10
        assert(
947 10
            $this->getModelSchemas()->hasRelationship($this->getModelClass(), $relationshipName),
948 10
            "Relationship `$relationshipName` do not exist in model `" . $this->getModelClass() . '`'
949
        );
950
951
        // as we read data from a relationship our main table and model would be the table/model in the relationship
952
        // so 'root' model(s) will be located in the reverse relationship.
953
954
        list ($targetModelClass, $reverseRelName) =
955 10
            $this->getModelSchemas()->getReverseRelationship($this->getModelClass(), $relationshipName);
956
957
        $builder = $this
958 10
            ->createBuilder($targetModelClass)
959 10
            ->selectModelColumns($columns)
960 10
            ->fromModelTable();
961
962
        // 'root' filters would be applied to the data in the reverse relationship ...
963 10
        if ($this->hasFilters() === true) {
964 10
            $filters = $this->getFilters();
965 10
            $sorts   = $this->getSorts();
966 10
            $this->areFiltersWithAnd() ?
967 9
                $builder->addRelationshipFiltersAndSortsWithAnd($reverseRelName, $filters, $sorts) :
968 1
                $builder->addRelationshipFiltersAndSortsWithOr($reverseRelName, $filters, $sorts);
0 ignored issues
show
Bug introduced by
It seems like $filters defined by $this->getFilters() on line 964 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...
969
        }
970
        // ... and the input filters to actual data we select
971 10
        if ($relationshipFilters !== null) {
972 7
            $builder->addFiltersWithAndToAlias($relationshipFilters);
973
        }
974 10
        if ($relationshipSorts !== null) {
975 3
            $builder->addSorts($relationshipSorts);
976
        }
977
978 10
        $this->applyPaging($builder);
979
980
        // While joining tables we select distinct rows.
981 10
        $builder->distinct();
982
983 10
        return $this->builderOnReadRelationship($builder);
984
    }
985
986
    /**
987
     * @inheritdoc
988
     */
989 9
    public function indexRelationship(
990
        string $name,
991
        iterable $relationshipFilters = null,
992
        iterable $relationshipSorts = null
993
    ) {
994 9
        assert(
995 9
            $this->getModelSchemas()->hasRelationship($this->getModelClass(), $name),
996 9
            "Relationship `$name` do not exist in model `" . $this->getModelClass() . '`'
997
        );
998
999
        // depending on the relationship type we expect the result to be either single resource or a collection
1000 9
        $relationshipType = $this->getModelSchemas()->getRelationshipType($this->getModelClass(), $name);
1001 9
        $isExpectMany     = $relationshipType === RelationshipTypes::HAS_MANY ||
1002 9
            $relationshipType === RelationshipTypes::BELONGS_TO_MANY;
1003
1004 9
        $builder = $this->createReadRelationshipBuilder($name, $relationshipFilters, $relationshipSorts);
1005
1006 9
        $modelClass = $builder->getModelClass();
1007 9
        $data       = $isExpectMany === true ?
1008 9
            $this->fetchResources($builder, $modelClass) : $this->fetchResource($builder, $modelClass);
1009
1010 9
        return $data;
1011
    }
1012
1013
    /**
1014
     * @inheritdoc
1015
     */
1016 2
    public function indexRelationshipIdentities(
1017
        string $name,
1018
        iterable $relationshipFilters = null,
1019
        iterable $relationshipSorts = null
1020
    ): array {
1021 2
        assert(
1022 2
            $this->getModelSchemas()->hasRelationship($this->getModelClass(), $name),
1023 2
            "Relationship `$name` do not exist in model `" . $this->getModelClass() . '`'
1024
        );
1025
1026
        // depending on the relationship type we expect the result to be either single resource or a collection
1027 2
        $relationshipType = $this->getModelSchemas()->getRelationshipType($this->getModelClass(), $name);
1028 2
        $isExpectMany     = $relationshipType === RelationshipTypes::HAS_MANY ||
1029 2
            $relationshipType === RelationshipTypes::BELONGS_TO_MANY;
1030 2
        if ($isExpectMany === false) {
1031 1
            throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT));
1032
        }
1033
1034 1
        list ($targetModelClass) = $this->getModelSchemas()->getReverseRelationship($this->getModelClass(), $name);
1035 1
        $targetPk = $this->getModelSchemas()->getPrimaryKey($targetModelClass);
1036
1037 1
        $builder = $this->createReadRelationshipBuilder($name, $relationshipFilters, $relationshipSorts, [$targetPk]);
0 ignored issues
show
Documentation introduced by
array($targetPk) is of type array<integer,?,{"0":"?"}>, 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...
1038
1039 1
        $modelClass = $builder->getModelClass();
1040
        /** @var Generator $data */
1041 1
        $data   = $this->fetchColumn($builder, $modelClass, $targetPk);
1042 1
        $result = iterator_to_array($data);
1043
1044 1
        return $result;
1045
    }
1046
1047
    /**
1048
     * @inheritdoc
1049
     */
1050 3
    public function readRelationship(
1051
        $index,
1052
        string $name,
1053
        iterable $relationshipFilters = null,
1054
        iterable $relationshipSorts = null
1055
    ) {
1056 3
        return $this->withIndexFilter($index)->indexRelationship($name, $relationshipFilters, $relationshipSorts);
1057
    }
1058
1059
    /**
1060
     * @inheritdoc
1061
     */
1062 6
    public function hasInRelationship($parentId, string $name, $childId): bool
1063
    {
1064 6
        if ($parentId !== null && is_scalar($parentId) === false) {
1065 1
            throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT));
1066
        }
1067 5
        if ($childId !== null && is_scalar($childId) === false) {
1068 1
            throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT));
1069
        }
1070
1071 4
        $parentPkName  = $this->getModelSchemas()->getPrimaryKey($this->getModelClass());
1072 4
        $parentFilters = [$parentPkName => [FilterParameterInterface::OPERATION_EQUALS => [$parentId]]];
1073 4
        list($childClass) = $this->getModelSchemas()->getReverseRelationship($this->getModelClass(), $name);
1074 4
        $childPkName  = $this->getModelSchemas()->getPrimaryKey($childClass);
1075 4
        $childFilters = [$childPkName => [FilterParameterInterface::OPERATION_EQUALS => [$childId]]];
1076
1077
        $data = $this
1078 4
            ->clearBuilderParameters()
1079 4
            ->clearFetchParameters()
1080 4
            ->withFilters($parentFilters)
0 ignored issues
show
Documentation introduced by
$parentFilters is of type array<?,array<string|int...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...
1081 4
            ->indexRelationship($name, $childFilters);
0 ignored issues
show
Documentation introduced by
$childFilters is of type array<?,array<string|int...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...
1082
1083 4
        $has = empty($data->getData()) === false;
1084
1085 4
        return $has;
1086
    }
1087
1088
    /**
1089
     * @inheritdoc
1090
     */
1091 1
    public function delete(): int
1092
    {
1093 1
        $deleted = $this->createDeleteBuilder()->execute();
1094
1095 1
        $this->clearFetchParameters();
1096
1097 1
        return (int)$deleted;
1098
    }
1099
1100
    /**
1101
     * @inheritdoc
1102
     */
1103 6
    public function remove($index): bool
1104
    {
1105 6
        $this->withIndexFilter($index);
1106
1107 5
        $deleted = $this->createDeleteBuilder()->execute();
1108
1109 4
        $this->clearFetchParameters();
1110
1111 4
        return (int)$deleted > 0;
1112
    }
1113
1114
    /**
1115
     * @inheritdoc
1116
     */
1117 5
    public function create($index, iterable $attributes, iterable $toMany): string
1118
    {
1119 5
        if ($index !== null && is_int($index) === false && is_string($index) === false) {
1120 1
            throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT));
1121
        }
1122
1123 4
        $allowedChanges = $this->filterAttributesOnCreate($index, $attributes);
1124
        $saveMain       = $this
1125 4
            ->createBuilder($this->getModelClass())
1126 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...
1127 4
        $saveMain       = $this->builderSaveResourceOnCreate($saveMain);
1128 4
        $saveMain->getSQL(); // prepare
1129
1130 4
        $this->clearBuilderParameters()->clearFetchParameters();
1131
1132 4
        $this->inTransaction(function () use ($saveMain, $toMany, &$index) {
1133 4
            $saveMain->execute();
1134
1135
            // if no index given will use last insert ID as index
1136 4
            $index !== null ?: $index = $saveMain->getConnection()->lastInsertId();
1137
1138 4
            $inserted = 0;
1139 4
            foreach ($toMany as $relationshipName => $secondaryIds) {
1140 2
                $secondaryIdBindName = ':secondaryId';
1141 2
                $saveToMany          = $this->builderSaveRelationshipOnCreate(
1142 2
                    $relationshipName,
1143
                    $this
1144 2
                        ->createBuilderFromConnection($saveMain->getConnection(), $this->getModelClass())
1145 2
                        ->prepareCreateInToManyRelationship($relationshipName, $index, $secondaryIdBindName)
1146
                );
1147 2
                foreach ($secondaryIds as $secondaryId) {
1148 2
                    $inserted += (int)$saveToMany->setParameter($secondaryIdBindName, $secondaryId)->execute();
1149
                }
1150
            }
1151 4
        });
1152
1153 4
        return $index;
1154
    }
1155
1156
    /**
1157
     * @inheritdoc
1158
     */
1159 5
    public function update($index, iterable $attributes, iterable $toMany): int
1160
    {
1161 5
        if (is_int($index) === false && is_string($index) === false) {
1162 1
            throw new InvalidArgumentException($this->getMessage(Messages::MSG_ERR_INVALID_ARGUMENT));
1163
        }
1164
1165 4
        $updated        = 0;
1166 4
        $pkName         = $this->getModelSchemas()->getPrimaryKey($this->getModelClass());
1167
        $filters        = [
1168
            $pkName => [
1169 4
                FilterParameterInterface::OPERATION_EQUALS => [$index],
1170
            ],
1171
        ];
1172 4
        $allowedChanges = $this->filterAttributesOnUpdate($attributes);
1173
        $saveMain       = $this
1174 4
            ->createBuilder($this->getModelClass())
1175 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...
1176 4
            ->addFiltersWithAndToTable($filters);
0 ignored issues
show
Documentation introduced by
$filters is of type array<?,array<string|int...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...
1177 4
        $saveMain       = $this->builderSaveResourceOnUpdate($saveMain);
1178 4
        $saveMain->getSQL(); // prepare
1179
1180 4
        $this->clearBuilderParameters()->clearFetchParameters();
1181
1182 4
        $this->inTransaction(function () use ($saveMain, $toMany, $index, &$updated) {
1183 4
            $updated = $saveMain->execute();
1184
1185 4
            foreach ($toMany as $relationshipName => $secondaryIds) {
1186 2
                $cleanToMany = $this->builderCleanRelationshipOnUpdate(
1187 2
                    $relationshipName,
1188
                    $this
1189 2
                        ->createBuilderFromConnection($saveMain->getConnection(), $this->getModelClass())
1190 2
                        ->clearToManyRelationship($relationshipName, $index)
1191
                );
1192 2
                $cleanToMany->execute();
1193
1194 2
                $secondaryIdBindName = ':secondaryId';
1195 2
                $saveToMany          = $this->builderSaveRelationshipOnUpdate(
1196 2
                    $relationshipName,
1197
                    $this
1198 2
                        ->createBuilderFromConnection($saveMain->getConnection(), $this->getModelClass())
1199 2
                        ->prepareCreateInToManyRelationship($relationshipName, $index, $secondaryIdBindName)
1200
                );
1201 2
                foreach ($secondaryIds as $secondaryId) {
1202 2
                    $updated += (int)$saveToMany->setParameter($secondaryIdBindName, $secondaryId)->execute();
1203
                }
1204
            }
1205 4
        });
1206
1207 4
        return (int)$updated;
1208
    }
1209
1210
    /**
1211
     * @return FactoryInterface
1212
     */
1213 47
    protected function getFactory(): FactoryInterface
1214
    {
1215 47
        return $this->factory;
1216
    }
1217
1218
    /**
1219
     * @return string
1220
     */
1221 48
    protected function getModelClass(): string
1222
    {
1223 48
        return $this->modelClass;
1224
    }
1225
1226
    /**
1227
     * @return ModelSchemaInfoInterface
1228
     */
1229 48
    protected function getModelSchemas(): ModelSchemaInfoInterface
1230
    {
1231 48
        return $this->modelSchemas;
1232
    }
1233
1234
    /**
1235
     * @return RelationshipPaginationStrategyInterface
1236
     */
1237 7
    protected function getRelationshipPagingStrategy(): RelationshipPaginationStrategyInterface
1238
    {
1239 7
        return $this->relPagingStrategy;
1240
    }
1241
1242
    /**
1243
     * @param Closure $closure
1244
     *
1245
     * @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...
1246
     *
1247
     * @throws DBALException
1248
     */
1249 8
    public function inTransaction(Closure $closure): void
1250
    {
1251 8
        $connection = $this->getConnection();
1252 8
        $connection->beginTransaction();
1253
        try {
1254 8
            $isOk = ($closure() === false ? null : true);
1255 8
        } finally {
1256 8
            isset($isOk) === true ? $connection->commit() : $connection->rollBack();
1257
        }
1258
    }
1259
1260
    /**
1261
     * @inheritdoc
1262
     */
1263 25
    public function fetchResources(QueryBuilder $builder, string $modelClass): PaginatedDataInterface
1264
    {
1265 25
        $data = $this->fetchPaginatedResourcesWithoutRelationships($builder, $modelClass);
1266
1267 25
        if ($this->hasIncludes() === true) {
1268 13
            $this->loadRelationships($data);
1269 13
            $this->clearFetchParameters();
1270
        }
1271
1272 25
        return $data;
1273
    }
1274
1275
    /**
1276
     * @inheritdoc
1277
     */
1278 13
    public function fetchResource(QueryBuilder $builder, string $modelClass)
1279
    {
1280 13
        $data = $this->fetchResourceWithoutRelationships($builder, $modelClass);
1281
1282 13
        if ($this->hasIncludes() === true) {
1283 7
            $this->loadRelationships($data);
1284 7
            $this->clearFetchParameters();
1285
        }
1286
1287 13
        return $data;
1288
    }
1289
1290
    /**
1291
     * @inheritdoc
1292
     *
1293
     * @SuppressWarnings(PHPMD.ElseExpression)
1294
     */
1295 2
    public function fetchRow(QueryBuilder $builder, string $modelClass): ?array
1296
    {
1297 2
        $model = null;
1298
1299 2
        $statement = $builder->execute();
1300 2
        $statement->setFetchMode(PDOConnection::FETCH_ASSOC);
1301
1302 2
        if (($attributes = $statement->fetch()) !== false) {
1303 2
            if ($this->isFetchTyped() === true) {
1304 1
                $platform  = $builder->getConnection()->getDatabasePlatform();
1305 1
                $typeNames = $this->getModelSchemas()->getAttributeTypes($modelClass);
1306 1
                $model     = $this->readRowFromAssoc($attributes, $typeNames, $platform);
1307
            } else {
1308 1
                $model = $attributes;
1309
            }
1310
        }
1311
1312 2
        $this->clearFetchParameters();
1313
1314 2
        return $model;
1315
    }
1316
1317
    /**
1318
     * @inheritdoc
1319
     *
1320
     * @SuppressWarnings(PHPMD.StaticAccess)
1321
     * @SuppressWarnings(PHPMD.ElseExpression)
1322
     */
1323 3
    public function fetchColumn(QueryBuilder $builder, string $modelClass, string $columnName): iterable
1324
    {
1325 3
        $statement = $builder->execute();
1326 3
        $statement->setFetchMode(PDOConnection::FETCH_ASSOC);
1327
1328 3
        if ($this->isFetchTyped() === true) {
1329 2
            $platform = $builder->getConnection()->getDatabasePlatform();
1330 2
            $typeName = $this->getModelSchemas()->getAttributeTypes($modelClass)[$columnName];
1331 2
            $type     = Type::getType($typeName);
1332 2
            while (($attributes = $statement->fetch()) !== false) {
1333 2
                $value     = $attributes[$columnName];
1334 2
                $converted = $type->convertToPHPValue($value, $platform);
1335
1336 2
                yield $converted;
1337
            }
1338
        } else {
1339 1
            while (($attributes = $statement->fetch()) !== false) {
1340 1
                $value = $attributes[$columnName];
1341
1342 1
                yield $value;
1343
            }
1344
        }
1345
1346 3
        $this->clearFetchParameters();
1347
    }
1348
1349
    /**
1350
     * @param QueryBuilder $builder
1351
     *
1352
     * @return ModelQueryBuilder
1353
     */
1354 2
    protected function createCountBuilderFromBuilder(QueryBuilder $builder): ModelQueryBuilder
1355
    {
1356 2
        $countBuilder = $this->createBuilder($this->getModelClass());
1357 2
        $countBuilder->setParameters($builder->getParameters());
1358 2
        $countBuilder->select('COUNT(*)')->from('(' . $builder->getSQL() . ') AS RESULT');
1359
1360 2
        return $countBuilder;
1361
    }
1362
1363
    /**
1364
     * @param QueryBuilder $builder
1365
     * @param string       $modelClass
1366
     *
1367
     * @return mixed|null
1368
     *
1369
     * @throws DBALException
1370
     *
1371
     * @SuppressWarnings(PHPMD.ElseExpression)
1372
     */
1373 13
    private function fetchResourceWithoutRelationships(QueryBuilder $builder, string $modelClass)
1374
    {
1375 13
        $model     = null;
1376 13
        $statement = $builder->execute();
1377
1378 13
        if ($this->isFetchTyped() === true) {
1379 11
            $statement->setFetchMode(PDOConnection::FETCH_ASSOC);
1380 11
            if (($attributes = $statement->fetch()) !== false) {
1381 11
                $platform  = $builder->getConnection()->getDatabasePlatform();
1382 11
                $typeNames = $this->getModelSchemas()->getAttributeTypes($modelClass);
1383 11
                $model     = $this->readResourceFromAssoc($modelClass, $attributes, $typeNames, $platform);
1384
            }
1385
        } else {
1386 2
            $statement->setFetchMode(PDOConnection::FETCH_CLASS, $modelClass);
1387 2
            if (($fetched = $statement->fetch()) !== false) {
1388 2
                $model = $fetched;
1389
            }
1390
        }
1391
1392 13
        return $model;
1393
    }
1394
1395
    /**
1396
     * @param QueryBuilder $builder
1397
     * @param string       $modelClass
1398
     * @param string       $keyColumnName
1399
     *
1400
     * @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...
1401
     *
1402
     * @throws DBALException
1403
     *
1404
     * @SuppressWarnings(PHPMD.ElseExpression)
1405
     */
1406 8
    private function fetchResourcesWithoutRelationships(
1407
        QueryBuilder $builder,
1408
        string $modelClass,
1409
        string $keyColumnName
1410
    ): iterable {
1411 8
        $statement = $builder->execute();
1412
1413 8
        if ($this->isFetchTyped() === true) {
1414 7
            $statement->setFetchMode(PDOConnection::FETCH_ASSOC);
1415 7
            $platform  = $builder->getConnection()->getDatabasePlatform();
1416 7
            $typeNames = $this->getModelSchemas()->getAttributeTypes($modelClass);
1417 7
            while (($attributes = $statement->fetch()) !== false) {
1418 6
                $model = $this->readResourceFromAssoc($modelClass, $attributes, $typeNames, $platform);
1419 6
                yield $model->{$keyColumnName} => $model;
1420
            }
1421
        } else {
1422 1
            $statement->setFetchMode(PDOConnection::FETCH_CLASS, $modelClass);
1423 1
            while (($model = $statement->fetch()) !== false) {
1424 1
                yield $model->{$keyColumnName} => $model;
1425
            }
1426
        }
1427
    }
1428
1429
    /**
1430
     * @param QueryBuilder $builder
1431
     * @param string       $modelClass
1432
     *
1433
     * @return PaginatedDataInterface
1434
     *
1435
     * @throws DBALException
1436
     */
1437 29
    private function fetchPaginatedResourcesWithoutRelationships(
1438
        QueryBuilder $builder,
1439
        string $modelClass
1440
    ): PaginatedDataInterface {
1441 29
        list($models, $hasMore, $limit, $offset) = $this->fetchResourceCollection($builder, $modelClass);
1442
1443 29
        $data = $this->getFactory()
1444 29
            ->createPaginatedData($models)
1445 29
            ->markAsCollection()
1446 29
            ->setOffset($offset)
1447 29
            ->setLimit($limit);
1448
1449 29
        $hasMore === true ? $data->markHasMoreItems() : $data->markHasNoMoreItems();
1450
1451 29
        return $data;
1452
    }
1453
1454
    /**
1455
     * @param QueryBuilder $builder
1456
     * @param string       $modelClass
1457
     *
1458
     * @return array
1459
     *
1460
     * @throws DBALException
1461
     *
1462
     * @SuppressWarnings(PHPMD.ElseExpression)
1463
     */
1464 29
    private function fetchResourceCollection(QueryBuilder $builder, string $modelClass): array
1465
    {
1466 29
        $statement = $builder->execute();
1467
1468 29
        $models           = [];
1469 29
        $counter          = 0;
1470 29
        $hasMoreThanLimit = false;
1471 29
        $limit            = $builder->getMaxResults() !== null ? $builder->getMaxResults() - 1 : null;
1472
1473 29
        if ($this->isFetchTyped() === true) {
1474 28
            $platform  = $builder->getConnection()->getDatabasePlatform();
1475 28
            $typeNames = $this->getModelSchemas()->getAttributeTypes($modelClass);
1476 28
            $statement->setFetchMode(PDOConnection::FETCH_ASSOC);
1477 28
            while (($attributes = $statement->fetch()) !== false) {
1478 27
                $counter++;
1479 27
                if ($limit !== null && $counter > $limit) {
1480 6
                    $hasMoreThanLimit = true;
1481 6
                    break;
1482
                }
1483 27
                $models[] = $this->readResourceFromAssoc($modelClass, $attributes, $typeNames, $platform);
1484
            }
1485
        } else {
1486 1
            $statement->setFetchMode(PDOConnection::FETCH_CLASS, $modelClass);
1487 1
            while (($fetched = $statement->fetch()) !== false) {
1488 1
                $counter++;
1489 1
                if ($limit !== null && $counter > $limit) {
1490 1
                    $hasMoreThanLimit = true;
1491 1
                    break;
1492
                }
1493 1
                $models[] = $fetched;
1494
            }
1495
        }
1496
1497 29
        return [$models, $hasMoreThanLimit, $limit, $builder->getFirstResult()];
1498
    }
1499
1500
    /**
1501
     * @param null|string $index
1502
     * @param iterable    $attributes
1503
     *
1504
     * @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...
1505
     */
1506 4
    protected function filterAttributesOnCreate(?string $index, iterable $attributes): iterable
1507
    {
1508 4
        if ($index !== null) {
1509 1
            $pkName = $this->getModelSchemas()->getPrimaryKey($this->getModelClass());
1510 1
            yield $pkName => $index;
1511
        }
1512
1513 4
        $knownAttrAndTypes = $this->getModelSchemas()->getAttributeTypes($this->getModelClass());
1514 4
        foreach ($attributes as $attribute => $value) {
1515 4
            if (array_key_exists($attribute, $knownAttrAndTypes) === true) {
1516 4
                yield $attribute => $value;
1517
            }
1518
        }
1519
    }
1520
1521
    /**
1522
     * @param iterable $attributes
1523
     *
1524
     * @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...
1525
     */
1526 4
    protected function filterAttributesOnUpdate(iterable $attributes): iterable
1527
    {
1528 4
        $knownAttrAndTypes = $this->getModelSchemas()->getAttributeTypes($this->getModelClass());
1529 4
        foreach ($attributes as $attribute => $value) {
1530 4
            if (array_key_exists($attribute, $knownAttrAndTypes) === true) {
1531 4
                yield $attribute => $value;
1532
            }
1533
        }
1534
    }
1535
1536
    /**
1537
     * @param TagStorageInterface   $modelsAtPath
1538
     * @param ArrayObject           $classAtPath
1539
     * @param ArrayObject           $idsAtPath
1540
     * @param ModelStorageInterface $deDup
1541
     * @param string                $parentsPath
1542
     * @param array                 $childRelationships
1543
     *
1544
     * @return void
1545
     *
1546
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
1547
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
1548
     *
1549
     * @throws DBALException
1550
     */
1551 9
    private function loadRelationshipsLayer(
1552
        TagStorageInterface $modelsAtPath,
1553
        ArrayObject $classAtPath,
1554
        ArrayObject $idsAtPath,
1555
        ModelStorageInterface $deDup,
1556
        string $parentsPath,
1557
        array $childRelationships
1558
    ): void {
1559 9
        $rootClass   = $classAtPath[static::ROOT_PATH];
1560 9
        $parentClass = $classAtPath[$parentsPath];
1561 9
        $parents     = $modelsAtPath->get($parentsPath);
1562
1563
        // What should we do? We have do find all child resources for $parents at paths $childRelationships (1 level
1564
        // child paths) and add them to $relationships. While doing it we have to deduplicate resources with
1565
        // $models.
1566
1567 9
        $pkName = $this->getModelSchemas()->getPrimaryKey($parentClass);
1568
1569 9
        $registerModelAtPath = function ($model, string $path) use ($deDup, $modelsAtPath, $idsAtPath) {
1570 8
            return self::registerModelAtPath(
1571 8
                $model,
1572 8
                $path,
1573 8
                $this->getModelSchemas(),
1574 8
                $deDup,
1575 8
                $modelsAtPath,
1576 8
                $idsAtPath
1577
            );
1578 9
        };
1579
1580 9
        foreach ($childRelationships as $name) {
1581 9
            $childrenPath = $parentsPath !== static::ROOT_PATH ? $parentsPath . static::PATH_SEPARATOR . $name : $name;
1582
1583 9
            $relationshipType = $this->getModelSchemas()->getRelationshipType($parentClass, $name);
1584
            list ($targetModelClass, $reverseRelName) =
1585 9
                $this->getModelSchemas()->getReverseRelationship($parentClass, $name);
1586
1587
            $builder = $this
1588 9
                ->createBuilder($targetModelClass)
1589 9
                ->selectModelColumns()
1590 9
                ->fromModelTable();
1591
1592 9
            $classAtPath[$childrenPath] = $targetModelClass;
1593
1594
            switch ($relationshipType) {
1595 9
                case RelationshipTypes::BELONGS_TO:
1596
                    // for 'belongsTo' relationship all resources could be read at once.
1597 8
                    $parentIds            = $idsAtPath[$parentsPath];
1598 8
                    $clonedBuilder        = (clone $builder)->addRelationshipFiltersAndSortsWithAnd(
1599 8
                        $reverseRelName,
1600 8
                        [$pkName => [FilterParameterInterface::OPERATION_IN => $parentIds]],
0 ignored issues
show
Documentation introduced by
array($pkName => array(\...TION_IN => $parentIds)) is of type array<?,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...
1601 8
                        null
1602
                    );
1603 8
                    $unregisteredChildren = $this->fetchResourcesWithoutRelationships(
1604 8
                        $clonedBuilder,
1605 8
                        $clonedBuilder->getModelClass(),
1606 8
                        $this->getModelSchemas()->getPrimaryKey($clonedBuilder->getModelClass())
1607
                    );
1608 8
                    $children             = [];
1609 8
                    foreach ($unregisteredChildren as $index => $unregisteredChild) {
1610 7
                        $children[$index] = $registerModelAtPath($unregisteredChild, $childrenPath);
1611
                    }
1612 8
                    $fkNameToChild = $this->getModelSchemas()->getForeignKey($parentClass, $name);
1613 8
                    foreach ($parents as $parent) {
1614 8
                        $fkToChild       = $parent->{$fkNameToChild};
1615 8
                        $parent->{$name} = $children[$fkToChild] ?? null;
1616
                    }
1617 8
                    break;
1618 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...
1619 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...
1620
                    // unfortunately we have paging limits for 'many' relationship thus we have read such
1621
                    // relationships for each 'parent' individually
1622 7
                    list ($queryOffset, $queryLimit) = $this->getRelationshipPagingStrategy()
1623 7
                        ->getParameters($rootClass, $parentClass, $parentsPath, $name);
1624 7
                    $builder->setFirstResult($queryOffset)->setMaxResults($queryLimit + 1);
1625 7
                    foreach ($parents as $parent) {
1626 7
                        $clonedBuilder = (clone $builder)->addRelationshipFiltersAndSortsWithAnd(
1627 7
                            $reverseRelName,
1628 7
                            [$pkName => [FilterParameterInterface::OPERATION_EQUALS => [$parent->{$pkName}]]],
0 ignored issues
show
Documentation introduced by
array($pkName => array(\...y($parent->{$pkName}))) is of type array<?,array<string|int...<integer,?,{"0":"?"}>>>, 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...
1629 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...
1630
                        );
1631 7
                        $children      = $this->fetchPaginatedResourcesWithoutRelationships(
1632 7
                            $clonedBuilder,
1633 7
                            $clonedBuilder->getModelClass()
1634
                        );
1635
1636 7
                        $deDupedChildren = [];
1637 7
                        foreach ($children->getData() as $child) {
1638 7
                            $deDupedChildren[] = $registerModelAtPath($child, $childrenPath);
1639
                        }
1640
1641 7
                        $paginated = $this->getFactory()
1642 7
                            ->createPaginatedData($deDupedChildren)
1643 7
                            ->markAsCollection()
1644 7
                            ->setOffset($children->getOffset())
1645 7
                            ->setLimit($children->getLimit());
1646 7
                        $children->hasMoreItems() === true ?
1647 7
                            $paginated->markHasMoreItems() : $paginated->markHasNoMoreItems();
1648
1649 7
                        $parent->{$name} = $paginated;
1650
                    }
1651 9
                    break;
1652
            }
1653
        }
1654
    }
1655
1656
    /**
1657
     * @param string $message
1658
     *
1659
     * @return string
1660
     *
1661
     * @throws ContainerExceptionInterface
1662
     * @throws NotFoundExceptionInterface
1663
     */
1664 8
    private function getMessage(string $message): string
1665
    {
1666
        /** @var FormatterFactoryInterface $factory */
1667 8
        $factory   = $this->getContainer()->get(FormatterFactoryInterface::class);
1668 8
        $formatter = $factory->createFormatter(FluteSettings::VALIDATION_NAMESPACE);
1669 8
        $result    = $formatter->formatMessage($message);
1670
1671 8
        return $result;
1672
    }
1673
1674
    /**
1675
     * @param string           $class
1676
     * @param array            $attributes
1677
     * @param Type[]           $typeNames
1678
     * @param AbstractPlatform $platform
1679
     *
1680
     * @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...
1681
     *
1682
     * @SuppressWarnings(PHPMD.StaticAccess)
1683
     *
1684
     * @throws DBALException
1685
     */
1686 34
    private function readResourceFromAssoc(
1687
        string $class,
1688
        array $attributes,
1689
        array $typeNames,
1690
        AbstractPlatform $platform
1691
    ) {
1692 34
        $instance = new $class();
1693 34
        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...
1694 34
            $instance->{$name} = $value;
1695
        }
1696
1697 34
        return $instance;
1698
    }
1699
1700
    /**
1701
     * @param array            $attributes
1702
     * @param Type[]           $typeNames
1703
     * @param AbstractPlatform $platform
1704
     *
1705
     * @return array
1706
     *
1707
     * @SuppressWarnings(PHPMD.StaticAccess)
1708
     *
1709
     * @throws DBALException
1710
     */
1711 1
    private function readRowFromAssoc(array $attributes, array $typeNames, AbstractPlatform $platform): array
1712
    {
1713 1
        $row = [];
1714 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...
1715 1
            $row[$name] = $value;
1716
        }
1717
1718 1
        return $row;
1719
    }
1720
1721
    /**
1722
     * @param iterable         $attributes
1723
     * @param array            $typeNames
1724
     * @param AbstractPlatform $platform
1725
     *
1726
     * @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...
1727
     *
1728
     * @throws DBALException
1729
     */
1730 35
    private function readTypedAttributes(iterable $attributes, array $typeNames, AbstractPlatform $platform): iterable
1731
    {
1732 35
        foreach ($attributes as $name => $value) {
1733 35
            yield $name => (array_key_exists($name, $typeNames) === true ?
1734 35
                Type::getType($typeNames[$name])->convertToPHPValue($value, $platform) : $value);
1735
        }
1736
    }
1737
}
1738