Completed
Push — master ( b75495...4cf1bc )
by Dmitry
02:58
created

ActiveQuery::getList()   C

Complexity

Conditions 8
Paths 24

Size

Total Lines 25
Code Lines 16

Duplication

Lines 10
Ratio 40 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 5
Bugs 2 Features 1
Metric Value
c 5
b 2
f 1
dl 10
loc 25
ccs 0
cts 23
cp 0
rs 5.3846
cc 8
eloc 16
nc 24
nop 3
crap 72
1
<?php
2
3
/*
4
 * Tools to use API as ActiveRecord for Yii2
5
 *
6
 * @link      https://github.com/hiqdev/yii2-hiart
7
 * @package   yii2-hiart
8
 * @license   BSD-3-Clause
9
 * @copyright Copyright (c) 2015-2016, HiQDev (http://hiqdev.com/)
10
 */
11
12
namespace hiqdev\hiart;
13
14
use Yii;
15
use yii\base\NotSupportedException;
16
use yii\db\ActiveQueryInterface;
17
use yii\db\ActiveQueryTrait;
18
use yii\db\ActiveRelationTrait;
19
use yii\helpers\ArrayHelper;
20
21
class ActiveQuery extends Query implements ActiveQueryInterface
22
{
23
    use ActiveQueryTrait {
24
        createModels as defaultCreateModels;
25
    }
26
27
    use ActiveRelationTrait;
28
29
    /**
30
     * @event Event an event that is triggered when the query is initialized via [[init()]].
31
     */
32
    const EVENT_INIT = 'init';
33
34
    /**
35
     * @var array a list of relations that this query should be joined with
36
     */
37
    public $joinWith = [];
38
39
    /**
40
     * @var array options for search
41
     */
42
    public $options = [];
43
44
    /**
45
     * Constructor.
46
     *
47
     * @param array $modelClass the model class associated with this query
48
     * @param array $config configurations to be applied to the newly created query object
49
     */
50
    public function __construct($modelClass, $config = [])
51
    {
52
        $this->modelClass = $modelClass;
0 ignored issues
show
Documentation Bug introduced by
It seems like $modelClass of type array is incompatible with the declared type string of property $modelClass.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
53
54
        parent::__construct($config);
55
    }
56
57
    /**
58
     * Initializes the object.
59
     * This method is called at the end of the constructor. The default implementation will trigger
60
     * an [[EVENT_INIT]] event. If you override this method, make sure you call the parent implementation at the end
61
     * to ensure triggering of the event.
62
     */
63
    public function init()
64
    {
65
        parent::init();
66
        $this->trigger(self::EVENT_INIT);
67
    }
68
69
    /**
70
     * Creates a DB command that can be used to execute this query.
71
     *
72
     * @param Connection $db the DB connection used to create the DB command.
73
     *                       If null, the DB connection returned by [[modelClass]] will be used.
74
     *
75
     * @return Command the created DB command instance.
76
     */
77
    public function createCommand($db = null)
78
    {
79
        if ($this->primaryModel !== null) {
80
            // lazy loading
81
            if (is_array($this->via)) {
82
                // via relation
83
                /* @var $viaQuery ActiveQuery */
84
                list($viaName, $viaQuery) = $this->via;
85
                if ($viaQuery->multiple) {
86
                    $viaModels = $viaQuery->all();
87
                    $this->primaryModel->populateRelation($viaName, $viaModels);
88
                } else {
89
                    $model = $viaQuery->one();
90
                    $this->primaryModel->populateRelation($viaName, $model);
91
                    $viaModels = $model === null ? [] : [$model];
92
                }
93
                $this->filterByModels($viaModels);
94
            } else {
95
                $this->filterByModels([$this->primaryModel]);
96
            }
97
        }
98
99
        /* @var $modelClass ActiveRecord */
100
        $modelClass = $this->modelClass;
101
        if ($db === null) {
102
            $db = $modelClass::getDb();
103
        }
104
105
        if ($this->type === null) {
106
            $this->type = $modelClass::type();
107
        }
108
        if ($this->index === null) {
109
            $this->index = $modelClass::index();
110
            $this->type = $modelClass::type();
111
        }
112
113
        $commandConfig = $db->getQueryBuilder()->build($this);
114
115
        return $db->createCommand($commandConfig);
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121
    public function prepare()
122
    {
123
        // NOTE: because the same ActiveQuery may be used to build different SQL statements
124
        // (e.g. by ActiveDataProvider, one for count query, the other for row data query,
125
        // it is important to make sure the same ActiveQuery can be used to build SQL statements
126
        // multiple times.
127
        if (!empty($this->joinWith)) {
128
            $this->buildJoinWith();
129
            $this->joinWith = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $joinWith.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
130
        }
131
    }
132
133
    public function joinWith($with)
134
    {
135
        $this->joinWith[] = [(array) $with, true];
136
137
        return $this;
138
    }
139
140
    private function buildJoinWith()
141
    {
142
        $join = $this->join;
143
144
        $this->join = [];
145
146
        foreach ($this->joinWith as $config) {
147
            list($with, $eagerLoading) = $config;
0 ignored issues
show
Unused Code introduced by
The assignment to $eagerLoading is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
148
149
            foreach ($with as $name => $callback) {
150
                if (is_int($name)) {
151
                    $this->join($callback);
152
                    unset($with[$name]);
153
                } else {
154
                    throw new NotSupportedException('joinWith() using query modification is not supported, use with() instead.');
155
                }
156
            }
157
        }
158
159
        // remove duplicated joins added by joinWithRelations that may be added
160
        // e.g. when joining a relation and a via relation at the same time
161
        $uniqueJoins = [];
162
        foreach ($this->join as $j) {
163
            $uniqueJoins[serialize($j)] = $j;
164
        }
165
        $this->join = array_values($uniqueJoins);
166
167
        if (!empty($join)) {
168
            // append explicit join to joinWith()
169
            // https://github.com/yiisoft/yii2/issues/2880
170
            $this->join = empty($this->join) ? $join : array_merge($this->join, $join);
171
        }
172
173
        if (empty($this->select)) {
174
            $this->addSelect(['*' => '*']);
175
            foreach ($this->joinWith as $join) {
176
                $this->addSelect(reset($join));
177
            }
178
        }
179
    }
180
181
    public function select($columns)
182
    {
183
        $this->select = $columns;
184
185
        return $this;
186
    }
187
188
    public function addSelect($columns)
189
    {
190
        if ($this->select === null) {
191
            $this->select = $columns;
192
        } else {
193
            $this->select = array_merge($this->select, $columns);
194
        }
195
196
        return $this;
197
    }
198
199
    /**
200
     * Executes query and returns all results as an array.
201
     *
202
     * @param Connection $db the DB connection used to create the DB command.
203
     *                            If null, the DB connection returned by [[modelClass]] will be used.
204
     *
205
     * @return array the query results. If the query results in nothing, an empty array will be returned.
206
     */
207
    public function all($db = null)
208
    {
209
        if ($this->asArray) {
210
            // TODO implement with
211
            return parent::all($db);
0 ignored issues
show
Bug introduced by
It seems like $db defined by parameter $db on line 207 can also be of type object<hiqdev\hiart\Connection>; however, hiqdev\hiart\Query::all() does only seem to accept object<yii\db\Connection>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
212
        }
213
214
        $result = $this->createCommand($db)->search($this->options);
215
216
        if (empty($result)) {
217
            return [];
218
        }
219
220
        $models = $this->createModels($result);
221
        if (!empty($this->with)) {
222
            $this->findWith($this->with, $models);
223
        }
224
        foreach ($models as $model) {
225
            $model->afterFind();
226
        }
227
228
        return $models;
229
    }
230
231
    private function createModels($rows)
232
    {
233
        $models = [];
234
        if ($this->asArray) {
235
            if ($this->indexBy === null) {
236
                return $rows;
237
            }
238
            foreach ($rows as $row) {
239 View Code Duplication
                if (is_string($this->indexBy)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
240
                    $key = $row[$this->indexBy];
241
                } else {
242
                    $key = call_user_func($this->indexBy, $row);
243
                }
244
                $models[$key] = $row;
245
            }
246
        } else {
247
            /* @var $class ActiveRecord */
248
            $class = $this->modelClass;
249
            if ($this->indexBy === null) {
250
                foreach ($rows as $row) {
251
                    $model = $class::instantiate($row);
252
                    $modelClass = get_class($model);
253
                    $modelClass::populateRecord($model, $row);
254
                    $this->populateJoinedRelations($model, $row);
255
                    $models[] = $model;
256
                }
257
            } else {
258
                foreach ($rows as $row) {
259
                    $model = $class::instantiate($row);
260
                    $modelClass = get_class($model);
261
                    $modelClass::populateRecord($model, $row);
262 View Code Duplication
                    if (is_string($this->indexBy)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
263
                        $key = $model->{$this->indexBy};
264
                    } else {
265
                        $key = call_user_func($this->indexBy, $model);
266
                    }
267
                    $models[$key] = $model;
268
                }
269
            }
270
        }
271
272
        return $models;
273
    }
274
275
    /**
276
     * Populates joined relations from [[join]] array.
277
     *
278
     * @param ActiveRecord $model
279
     * @param array $row
280
     */
281
    public function populateJoinedRelations($model, array $row)
282
    {
283
        foreach ($row as $key => $value) {
284
            if (empty($this->join) || !is_array($value) || $model->hasAttribute($key)) {
285
                continue;
286
            }
287
            foreach ($this->join as $name) {
288
                if ($model->isRelationPopulated($name)) {
289
                    continue 2;
290
                }
291
                $records = [];
292
                $relation = $model->getRelation($name);
293
                $relationClass = $relation->modelClass;
0 ignored issues
show
Bug introduced by
Accessing modelClass 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...
294
295
                if ($relation->multiple) {
0 ignored issues
show
Bug introduced by
Accessing multiple 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...
296
                    foreach ($value as $item) {
297
                        $relationModel = $relationClass::instantiate($item);
298
                        $relationModelClass = get_class($relationModel);
299
                        $relationModelClass::populateRecord($relationModel, $item);
300
                        $relation->populateJoinedRelations($relationModel, $item);
301
                        $records[] = $relationModel;
302
                    }
303
                } else {
304
                    $relationModel = $relationClass::instantiate($value);
305
                    $relationModelClass = get_class($relationModel);
306
                    $relationModelClass::populateRecord($relationModel, $value);
307
                    $relation->populateJoinedRelations($relationModel, $value);
308
                    $records = $relationModel;
309
                }
310
311
                $model->populateRelation($name, $records);
312
            }
313
        }
314
    }
315
316
    /**
317
     * Executes query and returns a single row of result.
318
     *
319
     * @param Connection $db the DB connection used to create the DB command.
320
     *                       If null, the DB connection returned by [[modelClass]] will be used.
321
     *
322
     * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
323
     *                                 the query result may be either an array or an ActiveRecord object. Null will be returned
324
     *                                 if the query results in nothing.
325
     */
326
    public function one($db = null)
327
    {
328
        //        $result = $this->createCommand($db)->get();
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
329
330
        $result = $this->createCommand($db)->search(ArrayHelper::merge(['limit' => 1], $this->options));
331
        if (empty($result)) {
332
            return null;
333
        }
334
        $result = reset($result);
335
336
        if ($this->asArray) {
337
            // TODO implement with()
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
338
//            /* @var $modelClass ActiveRecord */
339
//            $modelClass = $this->modelClass;
340
//            $model = $result['_source'];
341
//            $pk = $modelClass::primaryKey()[0];
342
//            if ($pk === '_id') {
343
//                $model['_id'] = $result['_id'];
344
//            }
345
//            $model['_score'] = $result['_score'];
346
//            if (!empty($this->with)) {
347
//                $models = [$model];
348
//                $this->findWith($this->with, $models);
349
//                $model = $models[0];
350
//            }
351
            return $result;
352
        }
353
354
        /* @var $class ActiveRecord */
355
        $class = $this->modelClass;
356
        $model = $class::instantiate($result);
357
        $class::populateRecord($model, $result);
358
        $this->populateJoinedRelations($model, $result);
359
        if (!empty($this->with)) {
360
            $models = [$model];
361
            $this->findWith($this->with, $models);
362
            $model = $models[0];
363
        }
364
        $model->afterFind();
365
366
        return $model;
367
    }
368
369
    /**
370
     * {@inheritdoc}
371
     */
372
    public function search($db = null, $options = [])
373
    {
374
        $result = $this->createCommand($db)->search($options);
375
        // TODO implement with() for asArray
376
        if (!empty($result) && !$this->asArray) {
377
            $models = $this->createModels($result);
378
            if (!empty($this->with)) {
379
                $this->findWith($this->with, $models);
380
            }
381
            foreach ($models as $model) {
382
                $model->afterFind();
383
            }
384
            $result = $models;
385
        }
386
387
        return $result;
388
    }
389
390
    /**
391
     * {@inheritdoc}
392
     */
393
    public function column($field, $db = null)
394
    {
395
        if ($field === '_id') {
396
            $command = $this->createCommand($db);
397
            $command->queryParts['fields'] = [];
398
            $command->queryParts['_source'] = false;
399
            $result = $command->search();
400
            if (empty($result['hits']['hits'])) {
401
                return [];
402
            }
403
            $column = [];
404
            foreach ($result['hits']['hits'] as $row) {
405
                $column[] = $row['_id'];
406
            }
407
408
            return $column;
409
        }
410
411
        return parent::column($field, $db);
412
    }
413
}
414