Lines 100.00% 5 / 5
Functions and Methods 100.00% 4 / 4
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
EagerResult 100.00% 5 / 5 100.00% 4 / 4 4 100.00% 1 / 1
 __construct 100.00% 2 / 2 100.00% 1 / 1 1
 fromModels 100.00% 1 / 1 100.00% 1 / 1 1
 fromCollection 100.00% 1 / 1 100.00% 1 / 1 1
 listToCollection 100.00% 1 / 1 100.00% 1 / 1 1
1<?php
2
3declare(strict_types=1);
4
5namespace BlueprintAU\Radiant\Relations;
6
7use BlueprintAU\Radiant\Collection;
8
9/**
10 * The result of one eager load: the related models plus, for through
11 * relations, the per-row parent key each model belongs to.
12 *
13 * This exists so {@see HasManyThrough} does not have to record its
14 * parent-key list on the relation instance itself. A relation object is
15 * CACHED and shared across eager loads (ModelQueryBuilder::$relationCache);
16 * per-call state on the instance meant two interleaved loads of the same
17 * through-relation — sequential in one coroutine, concurrent under
18 * Swoole/Fiber — could read each other's keys and distribute children to
19 * the wrong parents. Returning per-call state instead makes the whole
20 * eager-load path stateless with respect to the cached relation.
21 *
22 * @template TModel of \BlueprintAU\Radiant\Model The related model class.
23 */
24final class EagerResult
25{
26    /**
27     * The loaded related models, in query order.
28     *
29     * @var Collection<int, TModel>
30     */
31    public readonly Collection $models;
32
33    /**
34     * The parent key for each model, positionally paired with $models —
35     * index i of this list is the key of the parent that $models[i]
36     * belongs to. Null for relations that re-derive the key from the model
37     * itself (HasOne/HasMany/BelongsTo read the model's FK attribute);
38     * through relations set it (the key travels through the join, not on
39     * the related model).
40     *
41     * @var list<int|string|null|list<int|string|null>>|null
42     */
43    public readonly ?array $parentKeys;
44
45    /**
46     * @param  Collection<int, TModel>  $models
47     * @param  list<int|string|null|list<int|string|null>>|null  $parentKeys  The per-row parent keys, or null.
48     */
49    public function __construct(Collection $models, ?array $parentKeys = null)
50    {
51        $this->models = $models;
52        $this->parentKeys = $parentKeys;
53    }
54
55    /**
56     * A models-only result — no per-row parent keys.
57     *
58     * @template TRelatedModel of \BlueprintAU\Radiant\Model
59     *
60     * @param  list<TRelatedModel>|array<int,TRelatedModel>  $models
61     * @return self<TRelatedModel>
62     */
63    public static function fromModels(array $models): self
64    {
65        return new self(self::listToCollection($models));
66    }
67
68    /**
69     * A models-only result built from an EXISTING collection — no per-row
70     * parent keys.
71     *
72     * @template TRelatedModel of \BlueprintAU\Radiant\Model
73     *
74     * @param  Collection<int, TRelatedModel>  $models
75     * @return self<TRelatedModel>
76     */
77    public static function fromCollection(Collection $models): self
78    {
79        return new self($models);
80    }
81
82    /**
83     * Wrap a model list into a collection, preserving the element template.
84     *
85     * @template TRelatedModel of \BlueprintAU\Radiant\Model
86     *
87     * @param  list<TRelatedModel>|array<int,TRelatedModel>  $models
88     * @return Collection<int, TRelatedModel>
89     *
90     * @internal Construction detail of the eager-load path; not public API.
91     */
92    public static function listToCollection(array $models): Collection
93    {
94        return Collection::make(array_values($models));
95    }
96}