Passed
Pull Request — master (#354)
by Alexander
05:49 queued 02:56
created

ActiveRelationTrait::populateInverseRelation()   B

Complexity

Conditions 11
Paths 10

Size

Total Lines 43
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 11.727

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 11
eloc 27
c 3
b 0
f 0
nc 10
nop 4
dl 0
loc 43
ccs 18
cts 22
cp 0.8182
crap 11.727
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\ActiveRecord;
6
7
use Closure;
8
use ReflectionException;
9
use Stringable;
10
use Throwable;
11
use Yiisoft\Db\Exception\Exception;
12
use Yiisoft\Db\Exception\InvalidArgumentException;
13
use Yiisoft\Db\Exception\InvalidConfigException;
14
use Yiisoft\Db\Exception\NotSupportedException;
15
16
use function array_combine;
17
use function array_diff_key;
18
use function array_fill_keys;
19
use function array_filter;
20
use function array_intersect_key;
21
use function array_keys;
22
use function array_merge;
23
use function array_unique;
24
use function count;
25
use function is_array;
26
use function is_object;
27
use function is_scalar;
28
use function is_string;
29
use function key;
30
use function reset;
31
use function serialize;
32
33
/**
34
 * ActiveRelationTrait implements the common methods and properties for active record relational queries.
35
 */
36
trait ActiveRelationTrait
37
{
38
    private bool $multiple = false;
39
    private ActiveRecordInterface|null $primaryModel = null;
40
    /** @psalm-var string[] */
41
    private array $link = [];
42
    /**
43
     * @var string|null the name of the relation that is the inverse of this relation.
44
     *
45
     * For example, an order has a customer, which means the inverse of the "customer" relation is the "orders", and the
46
     * inverse of the "orders" relation is the "customer". If this property is set, the primary record(s) will be
47
     * referenced through the specified relation.
48
     *
49
     * For example, `$customer->orders[0]->customer` and `$customer` will be the same object, and accessing the customer
50
     * of an order will not trigger new DB query.
51
     *
52
     * This property is only used in relational context.
53
     *
54
     * {@see inverseOf()}
55
     */
56
    private string|null $inverseOf = null;
57
    private array|ActiveQuery|null $via = null;
58
    private array $viaMap = [];
59
60
    /**
61
     * Clones internal objects.
62
     */
63
    public function __clone()
64
    {
65
        /** make a clone of "via" object so that the same query object can be reused multiple times */
66
        if (is_object($this->via)) {
67
            $this->via = clone $this->via;
68
        } elseif (is_array($this->via)) {
69
            $this->via = [$this->via[0], clone $this->via[1], $this->via[2]];
70
        }
71
    }
72
73
    /**
74
     * Specifies the relation associated with the junction table.
75
     *
76
     * Use this method to specify a pivot record/table when declaring a relation in the {@see ActiveRecord} class:
77
     *
78
     * ```php
79
     * class Order extends ActiveRecord
80
     * {
81
     *    public function getOrderItems() {
82
     *        return $this->hasMany(OrderItem::class, ['order_id' => 'id']);
83
     *    }
84
     *
85
     *    public function getItems() {
86
     *        return $this->hasMany(Item::class, ['id' => 'item_id'])->via('orderItems');
87
     *    }
88
     * }
89
     * ```
90
     *
91
     * @param string $relationName the relation name. This refers to a relation declared in {@see primaryModel}.
92
     * @param callable|null $callable a PHP callback for customizing the relation associated with the junction table.
93
     * Its signature should be `function($query)`, where `$query` is the query to be customized.
94
     *
95
     * @return static the relation object itself.
96
     */
97
    public function via(string $relationName, callable $callable = null): static
98
    {
99
        $relation = $this->primaryModel?->relationQuery($relationName);
100
        $callableUsed = $callable !== null;
101
        $this->via = [$relationName, $relation, $callableUsed];
102 107
103
        if ($callableUsed) {
104 107
            $callable($relation);
105 107
        }
106 107
107
        return $this;
108 107
    }
109 73
110
    /**
111
     * Sets the name of the relation that is the inverse of this relation.
112 107
     *
113
     * For example, a customer has orders, which means the inverse of the "orders" relation is the "customer".
114
     *
115
     * If this property is set, the primary record(s) will be referenced through the specified relation.
116
     *
117
     * For example, `$customer->orders[0]->customer` and `$customer` will be the same object, and accessing the customer
118
     * of an order will not trigger a new DB query.
119
     *
120
     * Use this method when declaring a relation in the {@see ActiveRecord} class, e.g. in Customer model:
121
     *
122
     * ```php
123
     * public function getOrders()
124
     * {
125
     *     return $this->hasMany(Order::class, ['customer_id' => 'id'])->inverseOf('customer');
126
     * }
127
     * ```
128
     *
129
     * This also may be used for Order model, but with caution:
130
     *
131
     * ```php
132
     * public function getCustomer()
133
     * {
134
     *     return $this->hasOne(Customer::class, ['id' => 'customer_id'])->inverseOf('orders');
135
     * }
136
     * ```
137
     *
138
     * in this case result will depend on how order(s) was loaded.
139
     * Let's suppose customer has several orders. If only one order was loaded:
140
     *
141
     * ```php
142
     * $orderQuery = new ActiveQuery(Order::class, $db);
143
     * $orders = $orderQuery->where(['id' => 1])->all();
144
     * $customerOrders = $orders[0]->customer->orders;
145
     * ```
146
     *
147
     * variable `$customerOrders` will contain only one order. If orders was loaded like this:
148
     *
149
     * ```php
150
     * $orderQuery = new ActiveQuery(Order::class, $db);
151
     * $orders = $orderQuery->with('customer')->where(['customer_id' => 1])->all();
152
     * $customerOrders = $orders[0]->customer->orders;
153
     * ```
154
     *
155
     * variable `$customerOrders` will contain all orders of the customer.
156
     *
157
     * @param string $relationName the name of the relation that is the inverse of this relation.
158
     *
159
     * @return static the relation object itself.
160
     */
161
    public function inverseOf(string $relationName): static
162
    {
163
        $this->inverseOf = $relationName;
164
165
        return $this;
166 16
    }
167
168 16
    /**
169
     * Returns query records depends on {@see $multiple} .
170 16
     *
171
     * This method is invoked when a relation of an ActiveRecord is being accessed in a lazy fashion.
172
     *
173
     * @throws Exception
174
     * @throws InvalidArgumentException
175
     * @throws InvalidConfigException
176
     * @throws ReflectionException
177
     * @throws Throwable if the relation is invalid.
178
     *
179
     * @return ActiveRecordInterface|array|null the related record(s).
180
     */
181
    public function relatedRecords(): ActiveRecordInterface|array|null
182
    {
183
        return $this->multiple ? $this->all() : $this->onePopulate();
0 ignored issues
show
Bug introduced by
It seems like all() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

183
        return $this->multiple ? $this->/** @scrutinizer ignore-call */ all() : $this->onePopulate();
Loading history...
Bug introduced by
It seems like onePopulate() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

183
        return $this->multiple ? $this->all() : $this->/** @scrutinizer ignore-call */ onePopulate();
Loading history...
184
    }
185
186 101
    /**
187
     * If applicable, populate the query's primary model into the related records' inverse relationship.
188 101
     *
189 101
     * @param array $result the array of related records as generated by {@see populate()}
190 101
     *
191 101
     * @throws \Yiisoft\Definitions\Exception\InvalidConfigException
192
     */
193
    private function addInverseRelations(array &$result): void
194
    {
195
        if ($this->inverseOf === null) {
196
            return;
197
        }
198
199 101
        $relatedModel = reset($result);
200
201
        if ($relatedModel instanceof ActiveRecordInterface) {
202
            $inverseRelation = $relatedModel->relationQuery($this->inverseOf);
203
            $primaryModel = $inverseRelation->getMultiple() ? [$this->primaryModel] : $this->primaryModel;
204
205
            foreach ($result as $relatedModel) {
206
                $relatedModel->populateRelation($this->inverseOf, $primaryModel);
207 16
            }
208
        } else {
209 16
            $inverseRelation = $this->getARInstance()->relationQuery($this->inverseOf);
0 ignored issues
show
Bug introduced by
It seems like getARInstance() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

209
            $inverseRelation = $this->/** @scrutinizer ignore-call */ getARInstance()->relationQuery($this->inverseOf);
Loading history...
210
            $primaryModel = $inverseRelation->getMultiple() ? [$this->primaryModel] : $this->primaryModel;
211
212
            foreach ($result as &$relatedModel) {
213 16
                $relatedModel[$this->inverseOf] = $primaryModel;
214 16
            }
215 16
        }
216 16
    }
217
218 16
    /**
219 16
     * Finds the related records and populates them into the primary models.
220 16
     *
221
     * @param string $name the relation name
222
     * @param array $primaryModels primary models
223 8
     *
224 8
     * @throws InvalidArgumentException|InvalidConfigException|NotSupportedException|Throwable if {@see link()} is
225
     * invalid.
226
     * @throws Exception
227 8
     *
228 8
     * @return array the related models
229
     */
230
    public function populateRelation(string $name, array &$primaryModels): array
231 16
    {
232
        if ($this->via instanceof self) {
233
            $viaQuery = $this->via;
234
            $viaModels = $viaQuery->findJunctionRows($primaryModels);
235
            $this->filterByModels($viaModels);
236
        } elseif (is_array($this->via)) {
237
            [$viaName, $viaQuery] = $this->via;
238
239
            if ($viaQuery->asArray === null) {
240
                /** inherit asArray from primary query */
241
                $viaQuery->asArray($this->asArray);
242
            }
243
244
            $viaQuery->primaryModel = null;
245 139
            $viaModels = $viaQuery->populateRelation($viaName, $primaryModels);
246
            $this->filterByModels($viaModels);
247 139
        } else {
248
            $this->filterByModels($primaryModels);
249
        }
250
251 139
        if (!$this->multiple && count($primaryModels) === 1) {
252
            $model = $this->onePopulate();
253
            $primaryModel = reset($primaryModels);
254
255
            if ($primaryModel instanceof ActiveRecordInterface) {
256
                $primaryModel->populateRelation($name, $model);
257 12
            } else {
258 12
                $primaryModels[key($primaryModels)][$name] = $model;
259 12
            }
260 139
261
            if ($this->inverseOf !== null) {
262
                $this->populateInverseRelation($primaryModels, [$model], $name, $this->inverseOf);
263
            }
264
265
            return [$model];
266 72
        }
267
268 72
        /**
269
         * {@see https://github.com/yiisoft/yii2/issues/3197}
270 72
         *
271
         * delay indexing related models after buckets are built.
272
         */
273 72
        $indexBy = $this->getIndexBy();
0 ignored issues
show
Bug introduced by
It seems like getIndexBy() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

273
        /** @scrutinizer ignore-call */ 
274
        $indexBy = $this->getIndexBy();
Loading history...
274 72
        $this->indexBy(null);
0 ignored issues
show
Bug introduced by
It seems like indexBy() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

274
        $this->/** @scrutinizer ignore-call */ 
275
               indexBy(null);
Loading history...
275 72
        $models = $this->all();
276
277 139
        if (isset($viaModels, $viaQuery)) {
278
            $buckets = $this->buildBuckets($models, $viaModels, $viaQuery);
279
        } else {
280 139
            $buckets = $this->buildBuckets($models);
281 28
        }
282 28
283
        $this->indexBy($indexBy);
284 28
285 28
        if ($indexBy !== null && $this->multiple) {
286
            $buckets = $this->indexBuckets($buckets, $indexBy);
287 4
        }
288
289
        if (isset($viaQuery)) {
290 28
            $deepViaQuery = $viaQuery;
291 8
292
            while ($deepViaQuery->via) {
293
                $deepViaQuery = is_array($deepViaQuery->via) ? $deepViaQuery->via[1] : $deepViaQuery->via;
294 28
            }
295
296
            $link = $deepViaQuery->link;
297
        } else {
298
            $link = $this->link;
299
        }
300
301
        foreach ($primaryModels as $i => $primaryModel) {
302 123
            $keys = null;
303 123
304 123
            if ($this->multiple && count($link) === 1) {
305
                $primaryModelKey = reset($link);
306 123
307 72
                if ($primaryModel instanceof ActiveRecordInterface) {
308
                    $keys = $primaryModel->getAttribute($primaryModelKey);
309 123
                } else {
310
                    $keys = $primaryModel[$primaryModelKey] ?? null;
311
                }
312 123
            }
313
314 123
            if (is_array($keys)) {
315 17
                $value = [];
316
317
                foreach ($keys as $key) {
318 123
                    $key = $this->normalizeModelKey($key);
319 123
                    if (isset($buckets[$key])) {
320 72
                        $value[] = $buckets[$key];
321
                    }
322 72
                }
323 5
324
                if ($indexBy !== null) {
325
                    /** if indexBy is set, array_merge will cause renumbering of numeric array */
326 72
                    $value = array_replace(...$value);
327
                } else {
328
                    $value = array_merge(...$value);
329 123
                }
330 123
            } else {
331
                $key = $this->getModelKey($primaryModel, $link);
332
                $value = $buckets[$key] ?? ($this->multiple ? [] : null);
333
            }
334
335
            if ($primaryModel instanceof ActiveRecordInterface) {
336
                $primaryModel->populateRelation($name, $value);
337
            } else {
338
                $primaryModels[$i][$name] = $value;
339
            }
340
        }
341
342
        if ($this->inverseOf !== null) {
343
            $this->populateInverseRelation($primaryModels, $models, $name, $this->inverseOf);
344
        }
345
346 123
        return $models;
347 123
    }
348
349
    /**
350 123
     * @throws \Yiisoft\Definitions\Exception\InvalidConfigException
351 123
     */
352
    private function populateInverseRelation(
353 9
        array &$primaryModels,
354
        array $models,
355
        string $primaryName,
356 123
        string $name
357 8
    ): void {
358
        if (empty($models) || empty($primaryModels)) {
359
            return;
360 123
        }
361
362
        $model = reset($models);
363 12
364
        if ($model instanceof ActiveRecordInterface) {
365
            $this->populateInverseRelationToModels($models, $primaryModels, $name);
366
            return;
367
        }
368
369 12
        $primaryModel = reset($primaryModels);
370
371
        if ($primaryModel instanceof ActiveRecordInterface) {
372 12
            if ($this->multiple) {
373
                foreach ($primaryModels as $primaryModel) {
374
                    $models = $primaryModel->relation($primaryName);
375 12
                    $this->populateInverseRelationToModels($models, $primaryModels, $name);
376 12
                    $primaryModel->populateRelation($primaryName, $models);
377
                }
378 4
            } else {
379
                foreach ($primaryModels as $primaryModel) {
380
                    $models = [$primaryModel->relation($primaryName)];
381 12
                    $this->populateInverseRelationToModels($models, $primaryModels, $name);
382 8
                    $primaryModel->populateRelation($primaryName, $models[0]);
383 8
                }
384 8
            }
385 8
        } else {
386 8
            if ($this->multiple) {
387
                foreach ($primaryModels as &$primaryModel) {
388
                    $this->populateInverseRelationToModels($primaryModel[$primaryName], $primaryModels, $name);
389 8
                }
390 4
            } else {
391
                foreach ($primaryModels as &$primaryModel) {
392
                    $models = [$primaryModel[$primaryName]];
393
                    $this->populateInverseRelationToModels($models, $primaryModels, $name);
394
                    $primaryModel[$primaryName] = $models[0];
395 4
                }
396 4
            }
397 4
        }
398
    }
399
400
    private function populateInverseRelationToModels(array &$models, array $primaryModels, string $name): void
401 8
    {
402 8
        if (empty($models)) {
403 8
            return;
404 8
        }
405 8
406
        $model = reset($models);
407 4
        $isArray = is_array($model);
408
409
        /** @var ActiveQuery $relation */
410
        $relation = $isArray ? $this->getARInstance()->relationQuery($name) : $model->relationQuery($name);
411
        $buckets = $relation->buildBuckets($primaryModels);
412
        $link = $relation->getLink();
413
        $default = $relation->getMultiple() ? [] : null;
414
415
        if ($isArray) {
416
            /** @var array $model */
417
            foreach ($models as &$model) {
418
                $key = $this->getModelKey($model, $link);
419
                $model[$name] = $buckets[$key] ?? $default;
420 12
            }
421
        } else {
422 127
            /** @var ActiveRecordInterface $model */
423
            foreach ($models as $model) {
424
                $key = $this->getModelKey($model, $link);
425
                $model->populateRelation($name, $buckets[$key] ?? $default);
426
            }
427
        }
428
    }
429 127
430 72
    private function buildBuckets(
431 72
        array $models,
432 72
        array $viaModels = null,
433 72
        self $viaQuery = null
434
    ): array {
435 72
        if ($viaModels !== null) {
436 71
            $map = [];
437 71
            $linkValues = $this->link;
438 71
            $viaLink = $viaQuery->link ?? [];
439
            $viaLinkKeys = array_keys($viaLink);
440
            $viaVia = null;
441 72
442
            foreach ($viaModels as $viaModel) {
443 72
                $key1 = $this->getModelKey($viaModel, $viaLinkKeys);
444 72
                $key2 = $this->getModelKey($viaModel, $linkValues);
445 5
                $map[$key2][$key1] = true;
446 5
            }
447
448 5
            if ($viaQuery !== null) {
449
                $viaQuery->viaMap = $map;
450
                $viaVia = $viaQuery->getVia();
451
            }
452 127
453 127
            while ($viaVia) {
454
                /**
455 127
                 * @var ActiveQuery $viaViaQuery
456 72
                 *
457 71
                 * @psalm-suppress RedundantCondition
458 71
                 */
459 71
                $viaViaQuery = is_array($viaVia) ? $viaVia[1] : $viaVia;
460 71
                $map = $this->mapVia($map, $viaViaQuery->viaMap);
0 ignored issues
show
Bug introduced by
The property viaMap is declared private in Yiisoft\ActiveRecord\ActiveQuery and cannot be accessed from this context.
Loading history...
461
462
                $viaVia = $viaViaQuery->getVia();
463
            }
464
        }
465 127
466 127
        $buckets = [];
467 127
        $linkKeys = array_keys($this->link);
468
469
        if (isset($map)) {
470
            foreach ($models as $model) {
471 127
                $key = $this->getModelKey($model, $linkKeys);
472 56
                if (isset($map[$key])) {
473 56
                    foreach (array_keys($map[$key]) as $key2) {
474
                        /** @psalm-suppress InvalidArrayOffset */
475
                        $buckets[$key2][] = $model;
476
                    }
477 127
                }
478
            }
479
        } else {
480 5
            foreach ($models as $model) {
481
                $key = $this->getModelKey($model, $linkKeys);
482 5
                $buckets[$key][] = $model;
483
            }
484 5
        }
485 5
486 5
        if (!$this->multiple) {
487
            foreach ($buckets as $i => $bucket) {
488
                $buckets[$i] = reset($bucket);
489
            }
490 5
        }
491
492
        return $buckets;
493
    }
494
495
    private function mapVia(array $map, array $viaMap): array
496
    {
497
        $resultMap = [];
498
499
        foreach ($map as $key => $linkKeys) {
500
            $resultMap[$key] = [];
501
            foreach (array_keys($linkKeys) as $linkKey) {
502 17
                /** @psalm-suppress InvalidArrayOffset */
503
                $resultMap[$key] += $viaMap[$linkKey];
504 17
            }
505
        }
506 17
507 17
        return $resultMap;
508 17
    }
509 17
510 17
    /**
511
     * Indexes buckets by a column name.
512
     *
513
     * @param Closure|string $indexBy the name of the column by which the query results should be indexed by. This can
514 17
     * also be a {@see Closure} that returns the index value based on the given models data.
515
     */
516
    private function indexBuckets(array $buckets, Closure|string $indexBy): array
517
    {
518
        foreach ($buckets as &$models) {
519
            $models = ArArrayHelper::index($models, $indexBy);
520
        }
521
522 236
        return $buckets;
523
    }
524 236
525 36
    /**
526 12
     * @param array $attributes the attributes to prefix.
527
     *
528 32
     * @throws \Yiisoft\Definitions\Exception\InvalidConfigException
529 32
     */
530 32
    private function prefixKeyColumns(array $attributes): array
531
    {
532 32
        if (!empty($this->join) || !empty($this->joinWith)) {
533
            if (empty($this->from)) {
534
                $alias = $this->getARInstance()->getTableName();
535
            } else {
536 36
                foreach ($this->from as $alias => $table) {
537 36
                    if (!is_string($alias)) {
538 36
                        $alias = $table;
539
                    }
540
                    break;
541
                }
542
            }
543 236
544
            if (isset($alias)) {
545
                foreach ($attributes as $i => $attribute) {
546 236
                    $attributes[$i] = "$alias.$attribute";
547
                }
548 236
            }
549
        }
550 236
551
        return $attributes;
552 236
    }
553 236
554
    /**
555 232
     * @throws \Yiisoft\Definitions\Exception\InvalidConfigException
556 232
     */
557 232
    protected function filterByModels(array $models): void
558 228
    {
559
        $attributes = array_keys($this->link);
560 228
        $attributes = $this->prefixKeyColumns($attributes);
561
562
        $model = reset($models);
563 228
        $values = [];
564
565
        if (count($attributes) === 1) {
566
            /** single key */
567
            $attribute = reset($this->link);
568 232
569 232
            if ($model instanceof ActiveRecordInterface) {
570
                foreach ($models as $model) {
571
                    $value = $model->getAttribute($attribute);
572
573
                    if ($value !== null) {
574
                        if (is_array($value)) {
575 8
                            $values = [...$values, ...$value];
576
                        } else {
577 8
                            $values[] = $value;
578 8
                        }
579
                    }
580 8
                }
581 8
            } else {
582
                foreach ($models as $model) {
583
                    if (isset($model[$attribute])) {
584 8
                        $value = $model[$attribute];
585
586 8
                        if (is_array($value)) {
587
                            $values = [...$values, ...$value];
588
                        } else {
589
                            $values[] = $value;
590
                        }
591
                    }
592 236
                }
593 232
            }
594 232
595 232
            if (!empty($values)) {
596 232
                $scalarValues = array_filter($values, 'is_scalar');
597 228
                $nonScalarValues = array_diff_key($values, $scalarValues);
598
599 8
                $scalarValues = array_unique($scalarValues);
600
                $values = [...$scalarValues, ...$nonScalarValues];
601
            }
602
        } else {
603 232
            $nulls = array_fill_keys($this->link, null);
604 232
605
            if ($model instanceof ActiveRecordInterface) {
606
                foreach ($models as $model) {
607 236
                    $value = $model->getAttributes($this->link);
608 236
609
                    if (!empty($value)) {
610
                        $values[] = array_combine($attributes, array_merge($nulls, $value));
611
                    }
612
                }
613
            } else {
614
                foreach ($models as $model) {
615
                    $value = array_intersect_key($model, $nulls);
616 127
617
                    if (!empty($value)) {
618 127
                        $values[] = array_combine($attributes, array_merge($nulls, $value));
619
                    }
620 127
                }
621 127
            }
622
        }
623
624 127
        if (empty($values)) {
625
            $this->emulateExecution();
0 ignored issues
show
Bug introduced by
It seems like emulateExecution() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

625
            $this->/** @scrutinizer ignore-call */ 
626
                   emulateExecution();
Loading history...
626
            $this->andWhere('1=0');
0 ignored issues
show
Bug introduced by
It seems like andWhere() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

626
            $this->/** @scrutinizer ignore-call */ 
627
                   andWhere('1=0');
Loading history...
627
            return;
628 127
        }
629
630 127
        $this->andWhere(['in', $attributes, $values]);
631
    }
632
633
    private function getModelKey(ActiveRecordInterface|array $activeRecord, array $attributes): false|int|string
634
    {
635
        $key = [];
636
637
        if (is_array($activeRecord)) {
0 ignored issues
show
introduced by
The condition is_array($activeRecord) is always true.
Loading history...
638 127
            foreach ($attributes as $attribute) {
639
                if (isset($activeRecord[$attribute])) {
640 127
                    $key[] = $this->normalizeModelKey($activeRecord[$attribute]);
641
                }
642
            }
643
        } else {
644
            foreach ($attributes as $attribute) {
645
                $value = $activeRecord->getAttribute($attribute);
646
647
                if ($value !== null) {
648 127
                    $key[] = $this->normalizeModelKey($value);
649
                }
650
            }
651
        }
652
653
        if (count($key) > 1) {
654
            return serialize($key);
655
        }
656 28
657
        $key = reset($key);
658 28
659
        return is_scalar($key) ? $key : serialize($key);
660
    }
661
662 28
    /**
663
     * @param int|string|Stringable|null $value raw key value.
664
     *
665 28
     * @return int|string|null normalized key value.
666
     */
667 28
    private function normalizeModelKey(int|string|Stringable|null $value): int|string|null
668
    {
669
        if ($value instanceof Stringable) {
670
            /**
671
             * ensure matching to special objects, which are convertible to string, for cross-DBMS relations,
672 28
             * for example: `|MongoId`
673
             */
674
            $value = (string) $value;
675
        }
676
677
        return $value;
678
    }
679
680
    /**
681
     * @param array $primaryModels either array of AR instances or arrays.
682 39
     *
683
     * @throws Exception
684 39
     * @throws Throwable
685
     * @throws \Yiisoft\Definitions\Exception\InvalidConfigException
686
     */
687
    private function findJunctionRows(array $primaryModels): array
688
    {
689
        if (empty($primaryModels)) {
690
            return [];
691
        }
692 63
693
        $this->filterByModels($primaryModels);
694 63
695
        /* @var $primaryModel ActiveRecord */
696
        $primaryModel = reset($primaryModels);
697
698
        if (!$primaryModel instanceof ActiveRecordInterface) {
0 ignored issues
show
introduced by
$primaryModel is always a sub-type of Yiisoft\ActiveRecord\ActiveRecordInterface.
Loading history...
699
            /** when primaryModels are array of arrays (asArray case) */
700
            $primaryModel = $this->arClass;
0 ignored issues
show
Unused Code introduced by
The assignment to $primaryModel is dead and can be removed.
Loading history...
701
        }
702
703
        return $this->asArray()->all();
0 ignored issues
show
Bug introduced by
It seems like asArray() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

703
        return $this->/** @scrutinizer ignore-call */ asArray()->all();
Loading history...
704
    }
705
706 113
    public function getMultiple(): bool
707
    {
708 113
        return $this->multiple;
709
    }
710
711
    /**
712
     * @return ActiveRecordInterface|null the primary model of a relational query.
713
     *
714
     * This is used only in lazy loading with dynamic query options.
715
     */
716
    public function getPrimaryModel(): ActiveRecordInterface|null
717
    {
718
        return $this->primaryModel;
719 117
    }
720
721 117
    /**
722
     * @psalm-return string[]
723
     */
724 252
    public function getLink(): array
725
    {
726 252
        return $this->link;
727
    }
728 252
729
    public function getVia(): array|ActiveQueryInterface|null
730
    {
731 252
        return $this->via;
732
    }
733 252
734
    public function multiple(bool $value): self
735 252
    {
736
        $this->multiple = $value;
737
738 252
        return $this;
739
    }
740 252
741
    public function primaryModel(ActiveRecordInterface $value): self
742 252
    {
743
        $this->primaryModel = $value;
744
745
        return $this;
746
    }
747
748
    public function link(array $value): self
749
    {
750
        $this->link = $value;
751
752
        return $this;
753
    }
754
}
755