Completed
Push — 2.1 ( b44a46...4c2160 )
by
unknown
12:30
created

ActiveQueryTrait::normalizeRelations()   C

Complexity

Conditions 7
Paths 25

Size

Total Lines 33
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 20
cts 20
cp 1
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 22
nc 25
nop 2
crap 7
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 bool 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 bool $value whether to return the query results in terms of arrays instead of Active Records.
37
     * @return $this the query object itself
38
     */
39 128
    public function asArray($value = true)
40
    {
41 128
        $this->asArray = $value;
42 128
        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 105
    public function with()
83
    {
84 105
        $with = func_get_args();
85 105
        if (isset($with[0]) && is_array($with[0])) {
86
            // the parameter is given as an array
87 51
            $with = $with[0];
88
        }
89
90 105
        if (empty($this->with)) {
91 105
            $this->with = $with;
92 9
        } 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
                } else {
98 9
                    $this->with[$name] = $value;
99
                }
100
            }
101
        }
102
103 105
        return $this;
104
    }
105
106
    /**
107
     * Converts found rows into model instances.
108
     * @param array $rows
109
     * @return array|ActiveRecord[]
110
     * @since 2.0.11
111
     */
112 319
    protected function createModels($rows)
113
    {
114 319
        if ($this->asArray) {
115 65
            return $rows;
116
        } else {
117 309
            $models = [];
118
            /* @var $class ActiveRecord */
119 309
            $class = $this->modelClass;
120 309
            foreach ($rows as $row) {
121 309
                $model = $class::instantiate($row);
122 309
                $modelClass = get_class($model);
123 309
                $modelClass::populateRecord($model, $row);
124 309
                $models[] = $model;
125
            }
126 309
            return $models;
127
        }
128
    }
129
130
    /**
131
     * Finds records corresponding to one or multiple relations and populates them into the primary models.
132
     * @param array $with a list of relations that this query should be performed with. Please
133
     * refer to [[with()]] for details about specifying this parameter.
134
     * @param array|ActiveRecord[] $models the primary models (can be either AR instances or arrays)
135
     */
136 84
    public function findWith($with, &$models)
137
    {
138 84
        $primaryModel = reset($models);
139 84
        if (!$primaryModel instanceof ActiveRecordInterface) {
140
            /* @var $modelClass ActiveRecordInterface */
141 9
            $modelClass = $this->modelClass;
142 9
            $primaryModel = $modelClass::instance();
143
        }
144 84
        $relations = $this->normalizeRelations($primaryModel, $with);
0 ignored issues
show
Compatibility introduced by
$primaryModel of type object<yii\db\ActiveRecordInterface> is not a sub-type of object<yii\db\ActiveRecord>. It seems like you assume a concrete implementation of the interface yii\db\ActiveRecordInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
145
        /* @var $relation ActiveQuery */
146 84
        foreach ($relations as $name => $relation) {
147 84
            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...
148
                // inherit asArray from primary query
149 84
                $relation->asArray($this->asArray);
150
            }
151 84
            $relation->populateRelation($name, $models);
152
        }
153 84
    }
154
155
    /**
156
     * @param ActiveRecord $model
157
     * @param array $with
158
     * @return ActiveQueryInterface[]
159
     */
160 84
    private function normalizeRelations($model, $with)
161
    {
162 84
        $relations = [];
163 84
        foreach ($with as $name => $callback) {
164 84
            if (is_int($name)) {
165 84
                $name = $callback;
166 84
                $callback = null;
167
            }
168 84
            if (($pos = strpos($name, '.')) !== false) {
169
                // with sub-relations
170 12
                $childName = substr($name, $pos + 1);
171 12
                $name = substr($name, 0, $pos);
172
            } else {
173 84
                $childName = null;
174
            }
175
176 84
            if (!isset($relations[$name])) {
177 84
                $relation = $model->getRelation($name);
178 84
                $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...
179 84
                $relations[$name] = $relation;
180
            } else {
181 9
                $relation = $relations[$name];
182
            }
183
184 84
            if (isset($childName)) {
185 12
                $relation->with[$childName] = $callback;
186 84
            } elseif ($callback !== null) {
187 84
                call_user_func($callback, $relation);
188
            }
189
        }
190
191 84
        return $relations;
192
    }
193
}
194