Completed
Push — master ( 1a67d2...dfab50 )
by Neomerx
11:35 queued 22s
created

Crud::count()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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