Passed
Push — master ( 364bec...91edba )
by Wilmer
02:56
created

ActiveQueryTrait   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 199
Duplicated Lines 0 %

Test Coverage

Coverage 93.22%

Importance

Changes 0
Metric Value
eloc 57
dl 0
loc 199
ccs 55
cts 59
cp 0.9322
rs 10
c 0
b 0
f 0
wmc 24

7 Methods

Rating   Name   Duplication   Size   Complexity  
B normalizeRelations() 0 34 7
B with() 0 21 7
A getWith() 0 3 1
A isAsArray() 0 3 1
A findWith() 0 17 4
A asArray() 0 5 1
A createModels() 0 21 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\ActiveRecord;
6
7
use ReflectionException;
8
use Yiisoft\Db\Exception\Exception;
9
use Yiisoft\Db\Exception\InvalidArgumentException;
10
use Yiisoft\Db\Exception\InvalidConfigException;
11
use Yiisoft\Db\Exception\NotSupportedException;
12
13
use function get_class;
14
use function is_array;
15
use function is_int;
16
use function reset;
17
use function strpos;
18
use function substr;
19
20
trait ActiveQueryTrait
21
{
22
    private array $with = [];
23
    private ?bool $asArray = null;
24
25
    /**
26
     * Sets the {@see asArray} property.
27
     *
28
     * @param bool $value whether to return the query results in terms of arrays instead of Active Records.
29
     *
30
     * @return $this the query object itself.
31
     */
32 135
    public function asArray(?bool $value = true): self
33
    {
34 135
        $this->asArray = $value;
35
36 135
        return $this;
37
    }
38
39
    /**
40
     * Specifies the relations with which this query should be performed.
41
     *
42
     * The parameters to this method can be either one or multiple strings, or a single array of relation names and the
43
     * optional callbacks to customize the relations.
44
     *
45
     * A relation name can refer to a relation defined in {@see modelClass} or a sub-relation that stands for a relation
46
     * of a related record.
47
     *
48
     * For example, `orders.address` means the `address` relation defined in the model class corresponding to the
49
     * `orders` relation.
50
     *
51
     * The following are some usage examples:
52
     *
53
     * ```php
54
     * // find customers together with their orders and country
55
     * Customer::find()->with('orders', 'country')->all();
56
     * // find customers together with their orders and the orders' shipping address
57
     * Customer::find()->with('orders.address')->all();
58
     * // find customers together with their country and orders of status 1
59
     * Customer::find()->with([
60
     *     'orders' => function (ActiveQuery $query) {
61
     *         $query->andWhere('status = 1');
62
     *     },
63
     *     'country',
64
     * ])->all();
65
     * ```
66
     *
67
     * You can call `with()` multiple times. Each call will add relations to the existing ones.
68
     *
69
     * For example, the following two statements are equivalent:
70
     *
71
     * ```php
72
     * Customer::find()->with('orders', 'country')->all();
73
     * Customer::find()->with('orders')->with('country')->all();
74
     * ```
75
     * @param array|string $with
76
     *
77
     * @return $this the query object itself
78
     */
79 119
    public function with(...$with): self
80
    {
81 119
        if (isset($with[0]) && is_array($with[0])) {
82
            /** the parameter is given as an array */
83 56
            $with = $with[0];
84
        }
85
86 119
        if (empty($this->with)) {
87 119
            $this->with = $with;
88 12
        } elseif (!empty($with)) {
89 12
            foreach ($with as $name => $value) {
90 12
                if (is_int($name)) {
91
                    /** repeating relation is fine as normalizeRelations() handle it well */
92 8
                    $this->with[] = $value;
93
                } else {
94 4
                    $this->with[$name] = $value;
95
                }
96
            }
97
        }
98
99 119
        return $this;
100
    }
101
102
    /**
103
     * Converts found rows into model instances.
104
     *
105
     * @param array $rows
106
     *
107
     * @return ActiveRecord[]|array|null
108
     */
109 336
    protected function createModels(array $rows): ?array
110
    {
111 336
        if ($this->asArray) {
112 56
            return $rows;
113
        } else {
114 328
            $models = [];
115
116
            /* @var $class ActiveRecord */
117 328
            $class = $this->modelClass;
118
119 328
            foreach ($rows as $row) {
120 328
                $model = $class::instantiate($row);
121
122 328
                $modelClass = get_class($model);
123
124 328
                $modelClass::populateRecord($model, $row);
125
126 328
                $models[] = $model;
127
            }
128
129 328
            return $models;
130
        }
131
    }
132
133
    /**
134
     * Finds records corresponding to one or multiple relations and populates them into the primary models.
135
     *
136
     * @param array $with a list of relations that this query should be performed with. Please refer to {@see with()}
137
     * for details about specifying this parameter.
138
     * @param ActiveRecord[]|array $models the primary models (can be either AR instances or arrays)
139
     *
140
     * @throws Exception
141
     * @throws InvalidArgumentException
142
     * @throws InvalidConfigException
143
     * @throws NotSupportedException
144
     * @throws ReflectionException
145
     */
146 99
    public function findWith(array $with, array &$models): void
147
    {
148 99
        $primaryModel = reset($models);
149
150 99
        if (!$primaryModel instanceof ActiveRecordInterface) {
151 12
            $primaryModel = $this->modelClass::instance();
152
        }
153
154 99
        $relations = $this->normalizeRelations($primaryModel, $with);
155
156 99
        foreach ($relations as $name => $relation) {
157 99
            if ($relation->asArray === null) {
158
                /** inherit asArray from primary query */
159 99
                $relation->asArray($this->asArray);
160
            }
161
162 99
            $relation->populateRelation($name, $models);
163
        }
164 99
    }
165
166
    /**
167
     * @param ActiveRecord $model
168
     * @param array $with
169
     *
170
     * @throws ReflectionException
171
     * @throws InvalidArgumentException
172
     *
173
     * @return ActiveQuery[]|array
174
     */
175 99
    private function normalizeRelations(ActiveRecordInterface $model, array $with): array
176
    {
177 99
        $relations = [];
178
179 99
        foreach ($with as $name => $callback) {
180 99
            if (is_int($name)) {
181 99
                $name = $callback;
182 99
                $callback = null;
183
            }
184
185 99
            if (($pos = strpos($name, '.')) !== false) {
186
                /** with sub-relations */
187 16
                $childName = substr($name, $pos + 1);
188 16
                $name = substr($name, 0, $pos);
189
            } else {
190 99
                $childName = null;
191
            }
192
193 99
            if (!isset($relations[$name])) {
194 99
                $relation = $model->getRelation($name);
195 99
                $relation->primaryModel = null;
0 ignored issues
show
Bug introduced by
Accessing primaryModel on the interface Yiisoft\ActiveRecord\ActiveQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
196 99
                $relations[$name] = $relation;
197
            } else {
198 12
                $relation = $relations[$name];
199
            }
200
201 99
            if (isset($childName)) {
202 16
                $relation->with[$childName] = $callback;
203 99
            } elseif ($callback !== null) {
204 24
                $callback($relation);
205
            }
206
        }
207
208 99
        return $relations;
209
    }
210
211
    public function isAsArray(): ?bool
212
    {
213
        return $this->asArray;
214
    }
215
216
    public function getWith(): array
217
    {
218
        return $this->with;
219
    }
220
}
221