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