Completed
Push — activequery-alias2 ( d7e45e...ce3573 )
by Carsten
39:33 queued 33:21
created

ActiveQueryTrait::createModels()   D

Complexity

Conditions 9
Paths 6

Size

Total Lines 42
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 9
Metric Value
dl 0
loc 42
rs 4.909
ccs 33
cts 33
cp 1
cc 9
eloc 30
nc 6
nop 1
crap 9
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db;
9
10
/**
11
 * ActiveQueryTrait implements the common methods and properties for active record query classes.
12
 *
13
 * @author Qiang Xue <[email protected]>
14
 * @author Carsten Brandt <[email protected]>
15
 * @since 2.0
16
 */
17
trait ActiveQueryTrait
18
{
19
    /**
20
     * @var string the name of the ActiveRecord class.
21
     */
22
    public $modelClass;
23
    /**
24
     * @var array a list of relations that this query should be performed with
25
     */
26
    public $with;
27
    /**
28
     * @var boolean whether to return each record as an array. If false (default), an object
29
     * of [[modelClass]] will be created to represent each record.
30
     */
31
    public $asArray;
32
33
34
    /**
35
     * Sets the [[asArray]] property.
36
     * @param boolean $value whether to return the query results in terms of arrays instead of Active Records.
37
     * @return $this the query object itself
38
     */
39 67
    public function asArray($value = true)
40
    {
41 67
        $this->asArray = $value;
42 67
        return $this;
43
    }
44
45
    /**
46
     * Specifies the relations with which this query should be performed.
47
     *
48
     * The parameters to this method can be either one or multiple strings, or a single array
49
     * of relation names and the optional callbacks to customize the relations.
50
     *
51
     * A relation name can refer to a relation defined in [[modelClass]]
52
     * or a sub-relation that stands for a relation of a related record.
53
     * For example, `orders.address` means the `address` relation defined
54
     * in the model class corresponding to the `orders` relation.
55
     *
56
     * The following are some usage examples:
57
     *
58
     * ```php
59
     * // find customers together with their orders and country
60
     * Customer::find()->with('orders', 'country')->all();
61
     * // find customers together with their orders and the orders' shipping address
62
     * Customer::find()->with('orders.address')->all();
63
     * // find customers together with their country and orders of status 1
64
     * Customer::find()->with([
65
     *     'orders' => function (\yii\db\ActiveQuery $query) {
66
     *         $query->andWhere('status = 1');
67
     *     },
68
     *     'country',
69
     * ])->all();
70
     * ```
71
     *
72
     * You can call `with()` multiple times. Each call will add relations to the existing ones.
73
     * For example, the following two statements are equivalent:
74
     *
75
     * ```php
76
     * Customer::find()->with('orders', 'country')->all();
77
     * Customer::find()->with('orders')->with('country')->all();
78
     * ```
79
     *
80
     * @return $this the query object itself
81
     */
82 49
    public function with()
83
    {
84 49
        $with = func_get_args();
85 49
        if (isset($with[0]) && is_array($with[0])) {
86
            // the parameter is given as an array
87 21
            $with = $with[0];
88 21
        }
89
90 49
        if (empty($this->with)) {
91 49
            $this->with = $with;
92 49
        } elseif (!empty($with)) {
93 9
            foreach ($with as $name => $value) {
94 9
                if (is_int($name)) {
95
                    // repeating relation is fine as normalizeRelations() handle it well
96 6
                    $this->with[] = $value;
97 6
                } else {
98 3
                    $this->with[$name] = $value;
99
                }
100 9
            }
101 9
        }
102
103 49
        return $this;
104
    }
105
106
    /**
107
     * Converts found rows into model instances
108
     * @param array $rows
109
     * @return array|ActiveRecord[]
110
     */
111 197
    private function createModels($rows)
112
    {
113 197
        $models = [];
114 197
        if ($this->asArray) {
115 36
            if ($this->indexBy === null) {
0 ignored issues
show
Bug introduced by
The property indexBy does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
116 33
                return $rows;
117
            }
118 3
            foreach ($rows as $row) {
119 3
                if (is_string($this->indexBy)) {
120 3
                    $key = $row[$this->indexBy];
121 3
                } else {
122 3
                    $key = call_user_func($this->indexBy, $row);
123
                }
124 3
                $models[$key] = $row;
125 3
            }
126 3
        } else {
127
            /* @var $class ActiveRecord */
128 191
            $class = $this->modelClass;
129 191
            if ($this->indexBy === null) {
130 188
                foreach ($rows as $row) {
131 188
                    $model = $class::instantiate($row);
132 188
                    $modelClass = get_class($model);
133 188
                    $modelClass::populateRecord($model, $row);
134 188
                    $models[] = $model;
135 188
                }
136 188
            } else {
137 9
                foreach ($rows as $row) {
138 9
                    $model = $class::instantiate($row);
139 9
                    $modelClass = get_class($model);
140 9
                    $modelClass::populateRecord($model, $row);
141 9
                    if (is_string($this->indexBy)) {
142 9
                        $key = $model->{$this->indexBy};
143 9
                    } else {
144 3
                        $key = call_user_func($this->indexBy, $model);
145
                    }
146 9
                    $models[$key] = $model;
147 9
                }
148
            }
149
        }
150
151 194
        return $models;
152
    }
153
154
    /**
155
     * Finds records corresponding to one or multiple relations and populates them into the primary models.
156
     * @param array $with a list of relations that this query should be performed with. Please
157
     * refer to [[with()]] for details about specifying this parameter.
158
     * @param array|ActiveRecord[] $models the primary models (can be either AR instances or arrays)
159
     */
160 46
    public function findWith($with, &$models)
161
    {
162 46
        $primaryModel = new $this->modelClass;
163 46
        $relations = $this->normalizeRelations($primaryModel, $with);
164
        /* @var $relation ActiveQuery */
165 46
        foreach ($relations as $name => $relation) {
166 46
            if ($relation->asArray === null) {
0 ignored issues
show
Bug introduced by
Accessing asArray on the interface yii\db\ActiveQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
167
                // inherit asArray from primary query
168 46
                $relation->asArray($this->asArray);
169 46
            }
170 46
            $relation->populateRelation($name, $models);
171 46
        }
172 46
    }
173
174
    /**
175
     * @param ActiveRecord $model
176
     * @param array $with
177
     * @return ActiveQueryInterface[]
178
     */
179 49
    private function normalizeRelations($model, $with)
180
    {
181 46
        $relations = [];
182 46
        foreach ($with as $name => $callback) {
183 46
            if (is_int($name)) {
184 46
                $name = $callback;
185 49
                $callback = null;
186 46
            }
187 46
            if (($pos = strpos($name, '.')) !== false) {
188
                // with sub-relations
189 9
                $childName = substr($name, $pos + 1);
190 9
                $name = substr($name, 0, $pos);
191 9
            } else {
192 46
                $childName = null;
193
            }
194
195 46
            if (!isset($relations[$name])) {
196 46
                $relation = $model->getRelation($name);
197 46
                $relation->primaryModel = null;
0 ignored issues
show
Bug introduced by
Accessing primaryModel on the interface yii\db\ActiveQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
198 46
                $relations[$name] = $relation;
199 46
            } else {
200 9
                $relation = $relations[$name];
201
            }
202
203 46
            if (isset($childName)) {
204 9
                $relation->with[$childName] = $callback;
205 46
            } elseif ($callback !== null) {
206 18
                call_user_func($callback, $relation);
207 18
            }
208 46
        }
209
210 46
        return $relations;
211
    }
212
}
213