Completed
Push — ar-ascollection ( 578eb6 )
by Alexander
08:56
created

ActiveQueryTrait::asCollection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
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
     * @var string collection class to be instantiated and filled with results.
35
     * @since 2.0.10
36
     */
37
    public $collectionClass;
38
39
    /**
40
     * Sets the [[asArray]] property.
41
     * @param boolean $value whether to return the query results in terms of arrays instead of Active Records.
42
     * @return $this the query object itself
43
     */
44 84
    public function asArray($value = true)
45
    {
46 84
        $this->asArray = $value;
47 84
        return $this;
48
    }
49
50
    /**
51
     * Sets the [[collectionClass]] property.
52
     * @param string $className collection class to be instantiated and filled with results.
53
     * @return $this the query object itself
54
     * @since 2.0.10
55
     */
56 3
    public function asCollection($className)
57
    {
58 3
        $this->collectionClass = $className;
59 3
        return $this;
60
    }
61
62
    /**
63
     * Specifies the relations with which this query should be performed.
64
     *
65
     * The parameters to this method can be either one or multiple strings, or a single array
66
     * of relation names and the optional callbacks to customize the relations.
67
     *
68
     * A relation name can refer to a relation defined in [[modelClass]]
69
     * or a sub-relation that stands for a relation of a related record.
70
     * For example, `orders.address` means the `address` relation defined
71
     * in the model class corresponding to the `orders` relation.
72
     *
73
     * The following are some usage examples:
74
     *
75
     * ```php
76
     * // find customers together with their orders and country
77
     * Customer::find()->with('orders', 'country')->all();
78
     * // find customers together with their orders and the orders' shipping address
79
     * Customer::find()->with('orders.address')->all();
80
     * // find customers together with their country and orders of status 1
81
     * Customer::find()->with([
82
     *     'orders' => function (\yii\db\ActiveQuery $query) {
83
     *         $query->andWhere('status = 1');
84
     *     },
85
     *     'country',
86
     * ])->all();
87
     * ```
88
     *
89
     * You can call `with()` multiple times. Each call will add relations to the existing ones.
90
     * For example, the following two statements are equivalent:
91
     *
92
     * ```php
93
     * Customer::find()->with('orders', 'country')->all();
94
     * Customer::find()->with('orders')->with('country')->all();
95
     * ```
96
     *
97
     * @return $this the query object itself
98
     */
99 78
    public function with()
100
    {
101 78
        $with = func_get_args();
102 78
        if (isset($with[0]) && is_array($with[0])) {
103
            // the parameter is given as an array
104 39
            $with = $with[0];
105 39
        }
106
107 78
        if (empty($this->with)) {
108 78
            $this->with = $with;
109 78
        } elseif (!empty($with)) {
110 9
            foreach ($with as $name => $value) {
111 9
                if (is_int($name)) {
112
                    // repeating relation is fine as normalizeRelations() handle it well
113 6
                    $this->with[] = $value;
114 6
                } else {
115 3
                    $this->with[$name] = $value;
116
                }
117 9
            }
118 9
        }
119
120 78
        return $this;
121
    }
122
123
    /**
124
     * Converts found rows into model instances
125
     * @param array $rows
126
     * @return array|ActiveRecord[]
127
     */
128 245
    private function createModels($rows)
129
    {
130 245
        $models = [];
131 245
        if ($this->asArray) {
132 39
            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...
133 36
                return $rows;
134
            }
135 3
            foreach ($rows as $row) {
136 3
                if (is_string($this->indexBy)) {
137 3
                    $key = $row[$this->indexBy];
138 3
                } else {
139 3
                    $key = call_user_func($this->indexBy, $row);
140
                }
141 3
                $models[$key] = $row;
142 3
            }
143 3
        } else {
144
            /* @var $class ActiveRecord */
145 239
            $class = $this->modelClass;
146 239
            if ($this->indexBy === null) {
147 236
                foreach ($rows as $row) {
148 236
                    $model = $class::instantiate($row);
149 236
                    $modelClass = get_class($model);
150 236
                    $modelClass::populateRecord($model, $row);
151 236
                    $models[] = $model;
152 236
                }
153 236
            } else {
154 12
                foreach ($rows as $row) {
155 12
                    $model = $class::instantiate($row);
156 12
                    $modelClass = get_class($model);
157 12
                    $modelClass::populateRecord($model, $row);
158 12
                    if (is_string($this->indexBy)) {
159 12
                        $key = $model->{$this->indexBy};
160 12
                    } else {
161 3
                        $key = call_user_func($this->indexBy, $model);
162
                    }
163 12
                    $models[$key] = $model;
164 12
                }
165
            }
166
        }
167
168 242
        return $models;
169
    }
170
171
    /**
172
     * Finds records corresponding to one or multiple relations and populates them into the primary models.
173
     * @param array $with a list of relations that this query should be performed with. Please
174
     * refer to [[with()]] for details about specifying this parameter.
175
     * @param array|ActiveRecord[] $models the primary models (can be either AR instances or arrays)
176
     */
177 63
    public function findWith($with, &$models)
178
    {
179 63
        $primaryModel = reset($models);
180 63
        if (!$primaryModel instanceof ActiveRecordInterface) {
181 6
            $primaryModel = new $this->modelClass;
182 6
        }
183 63
        $relations = $this->normalizeRelations($primaryModel, $with);
184
        /* @var $relation ActiveQuery */
185 63
        foreach ($relations as $name => $relation) {
186 63
            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...
187
                // inherit asArray from primary query
188 63
                $relation->asArray($this->asArray);
189 63
            }
190 63
            $relation->populateRelation($name, $models);
191 63
        }
192 63
    }
193
194
    /**
195
     * @param ActiveRecord $model
196
     * @param array $with
197
     * @return ActiveQueryInterface[]
198
     */
199 66
    private function normalizeRelations($model, $with)
200
    {
201 63
        $relations = [];
202 63
        foreach ($with as $name => $callback) {
203 63
            if (is_int($name)) {
204 63
                $name = $callback;
205 66
                $callback = null;
206 63
            }
207 63
            if (($pos = strpos($name, '.')) !== false) {
208
                // with sub-relations
209 9
                $childName = substr($name, $pos + 1);
210 9
                $name = substr($name, 0, $pos);
211 9
            } else {
212 63
                $childName = null;
213
            }
214
215 63
            if (!isset($relations[$name])) {
216 63
                $relation = $model->getRelation($name);
217 63
                $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...
218 63
                $relations[$name] = $relation;
219 63
            } else {
220 9
                $relation = $relations[$name];
221
            }
222
223 63
            if (isset($childName)) {
224 9
                $relation->with[$childName] = $callback;
225 63
            } elseif ($callback !== null) {
226 18
                call_user_func($callback, $relation);
227 18
            }
228 63
        }
229
230 63
        return $relations;
231
    }
232
}
233