Completed
Push — develop ( 46135c...90e0de )
by Neomerx
03:10
created

Crud::createCountBuilderFromBuilder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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