GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 2ab419...940f7c )
by Robert
32:50
created

ActiveQuery   D

Complexity

Total Complexity 109

Size/Duplication

Total Lines 725
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 90.7%

Importance

Changes 0
Metric Value
wmc 109
lcom 1
cbo 8
dl 0
loc 725
ccs 312
cts 344
cp 0.907
rs 4.4444
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A init() 0 5 1
A all() 0 4 1
C prepare() 0 61 11
C populate() 0 26 8
C removeDuplicatedModels() 0 46 11
A one() 0 10 3
A createCommand() 0 17 3
A queryScalar() 0 16 3
B joinWith() 0 31 6
C buildJoinWith() 0 41 11
A innerJoinWith() 0 4 1
D joinWithRelations() 0 42 10
A getJoinType() 0 8 4
B getQueryTableName() 0 25 5
F joinWithRelation() 0 67 18
A onCondition() 0 6 1
A viaTable() 0 15 2
B alias() 0 19 5
A andOnCondition() 0 10 2
A orOnCondition() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like ActiveQuery often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ActiveQuery, and based on these observations, apply Extract Interface, too.

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
use yii\base\InvalidConfigException;
11
12
/**
13
 * ActiveQuery represents a DB query associated with an Active Record class.
14
 *
15
 * An ActiveQuery can be a normal query or be used in a relational context.
16
 *
17
 * ActiveQuery instances are usually created by [[ActiveRecord::find()]] and [[ActiveRecord::findBySql()]].
18
 * Relational queries are created by [[ActiveRecord::hasOne()]] and [[ActiveRecord::hasMany()]].
19
 *
20
 * Normal Query
21
 * ------------
22
 *
23
 * ActiveQuery mainly provides the following methods to retrieve the query results:
24
 *
25
 * - [[one()]]: returns a single record populated with the first row of data.
26
 * - [[all()]]: returns all records based on the query results.
27
 * - [[count()]]: returns the number of records.
28
 * - [[sum()]]: returns the sum over the specified column.
29
 * - [[average()]]: returns the average over the specified column.
30
 * - [[min()]]: returns the min over the specified column.
31
 * - [[max()]]: returns the max over the specified column.
32
 * - [[scalar()]]: returns the value of the first column in the first row of the query result.
33
 * - [[column()]]: returns the value of the first column in the query result.
34
 * - [[exists()]]: returns a value indicating whether the query result has data or not.
35
 *
36
 * Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]],
37
 * [[orderBy()]] to customize the query options.
38
 *
39
 * ActiveQuery also provides the following additional query options:
40
 *
41
 * - [[with()]]: list of relations that this query should be performed with.
42
 * - [[joinWith()]]: reuse a relation query definition to add a join to a query.
43
 * - [[indexBy()]]: the name of the column by which the query result should be indexed.
44
 * - [[asArray()]]: whether to return each record as an array.
45
 *
46
 * These options can be configured using methods of the same name. For example:
47
 *
48
 * ```php
49
 * $customers = Customer::find()->with('orders')->asArray()->all();
50
 * ```
51
 *
52
 * Relational query
53
 * ----------------
54
 *
55
 * In relational context ActiveQuery represents a relation between two Active Record classes.
56
 *
57
 * Relational ActiveQuery instances are usually created by calling [[ActiveRecord::hasOne()]] and
58
 * [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
59
 * a getter method which calls one of the above methods and returns the created ActiveQuery object.
60
 *
61
 * A relation is specified by [[link]] which represents the association between columns
62
 * of different tables; and the multiplicity of the relation is indicated by [[multiple]].
63
 *
64
 * If a relation involves a junction table, it may be specified by [[via()]] or [[viaTable()]] method.
65
 * These methods may only be called in a relational context. Same is true for [[inverseOf()]], which
66
 * marks a relation as inverse of another relation and [[onCondition()]] which adds a condition that
67
 * is to be added to relational query join condition.
68
 *
69
 * @author Qiang Xue <[email protected]>
70
 * @author Carsten Brandt <[email protected]>
71
 * @since 2.0
72
 */
73
class ActiveQuery extends Query implements ActiveQueryInterface
74
{
75
    use ActiveQueryTrait;
76
    use ActiveRelationTrait;
77
78
    /**
79
     * @event Event an event that is triggered when the query is initialized via [[init()]].
80
     */
81
    const EVENT_INIT = 'init';
82
83
    /**
84
     * @var string the SQL statement to be executed for retrieving AR records.
85
     * This is set by [[ActiveRecord::findBySql()]].
86
     */
87
    public $sql;
88
    /**
89
     * @var string|array the join condition to be used when this query is used in a relational context.
90
     * The condition will be used in the ON part when [[ActiveQuery::joinWith()]] is called.
91
     * Otherwise, the condition will be used in the WHERE part of a query.
92
     * Please refer to [[Query::where()]] on how to specify this parameter.
93
     * @see onCondition()
94
     */
95
    public $on;
96
    /**
97
     * @var array a list of relations that this query should be joined with
98
     */
99
    public $joinWith;
100
101
102
    /**
103
     * Constructor.
104
     * @param string $modelClass the model class associated with this query
105
     * @param array $config configurations to be applied to the newly created query object
106
     */
107 343
    public function __construct($modelClass, $config = [])
108
    {
109 343
        $this->modelClass = $modelClass;
110 343
        parent::__construct($config);
111 343
    }
112
113
    /**
114
     * Initializes the object.
115
     * This method is called at the end of the constructor. The default implementation will trigger
116
     * an [[EVENT_INIT]] event. If you override this method, make sure you call the parent implementation at the end
117
     * to ensure triggering of the event.
118
     */
119 343
    public function init()
120
    {
121 343
        parent::init();
122 343
        $this->trigger(self::EVENT_INIT);
123 343
    }
124
125
    /**
126
     * Executes query and returns all results as an array.
127
     * @param Connection $db the DB connection used to create the DB command.
128
     * If null, the DB connection returned by [[modelClass]] will be used.
129
     * @return array|ActiveRecord[] the query results. If the query results in nothing, an empty array will be returned.
130
     */
131 164
    public function all($db = null)
132
    {
133 164
        return parent::all($db);
134
    }
135
136
    /**
137
     * @inheritdoc
138
     */
139 293
    public function prepare($builder)
140
    {
141
        // NOTE: because the same ActiveQuery may be used to build different SQL statements
142
        // (e.g. by ActiveDataProvider, one for count query, the other for row data query,
143
        // it is important to make sure the same ActiveQuery can be used to build SQL statements
144
        // multiple times.
145 293
        if (!empty($this->joinWith)) {
146 36
            $this->buildJoinWith();
147 36
            $this->joinWith = null;    // clean it up to avoid issue https://github.com/yiisoft/yii2/issues/2687
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...
148 36
        }
149
150 293
        if (empty($this->from)) {
151
            /* @var $modelClass ActiveRecord */
152 293
            $modelClass = $this->modelClass;
153 293
            $tableName = $modelClass::tableName();
154 293
            $this->from = [$tableName];
155 293
        }
156
157 293
        if (empty($this->select) && !empty($this->join)) {
158 33
            list(, $alias) = $this->getQueryTableName($this);
159 33
            $this->select = ["$alias.*"];
160 33
        }
161
162 293
        if ($this->primaryModel === null) {
163
            // eager loading
164 293
            $query = Query::create($this);
165 293
        } else {
166
            // lazy loading of a relation
167 81
            $where = $this->where;
168
169 81
            if ($this->via instanceof self) {
170
                // via junction table
171 15
                $viaModels = $this->via->findJunctionRows([$this->primaryModel]);
172 15
                $this->filterByModels($viaModels);
173 81
            } elseif (is_array($this->via)) {
174
                // via relation
175
                /* @var $viaQuery ActiveQuery */
176 27
                list($viaName, $viaQuery) = $this->via;
177 27
                if ($viaQuery->multiple) {
178 27
                    $viaModels = $viaQuery->all();
179 27
                    $this->primaryModel->populateRelation($viaName, $viaModels);
180 27
                } else {
181
                    $model = $viaQuery->one();
182
                    $this->primaryModel->populateRelation($viaName, $model);
183
                    $viaModels = $model === null ? [] : [$model];
184
                }
185 27
                $this->filterByModels($viaModels);
186 27
            } else {
187 72
                $this->filterByModels([$this->primaryModel]);
188
            }
189
190 81
            $query = Query::create($this);
191 81
            $this->where = $where;
192
        }
193
194 293
        if (!empty($this->on)) {
195 12
            $query->andWhere($this->on);
196 12
        }
197
198 293
        return $query;
199
    }
200
201
    /**
202
     * @inheritdoc
203
     */
204 258
    public function populate($rows)
205
    {
206 258
        if (empty($rows)) {
207 43
            return [];
208
        }
209
210 252
        $models = $this->createModels($rows);
211 252
        if (!empty($this->join) && $this->indexBy === null) {
212 24
            $models = $this->removeDuplicatedModels($models);
213 24
        }
214 252
        if (!empty($this->with)) {
215 63
            $this->findWith($this->with, $models);
216 63
        }
217
218 252
        if ($this->inverseOf !== null) {
219 6
            $this->addInverseRelations($models);
220 6
        }
221
222 252
        if (!$this->asArray) {
223 246
            foreach ($models as $model) {
224 246
                $model->afterFind();
225 246
            }
226 246
        }
227
228 252
        return $models;
229
    }
230
231
    /**
232
     * Removes duplicated models by checking their primary key values.
233
     * This method is mainly called when a join query is performed, which may cause duplicated rows being returned.
234
     * @param array $models the models to be checked
235
     * @throws InvalidConfigException if model primary key is empty
236
     * @return array the distinctive models
237
     */
238 24
    private function removeDuplicatedModels($models)
239
    {
240 24
        $hash = [];
241
        /* @var $class ActiveRecord */
242 24
        $class = $this->modelClass;
243 24
        $pks = $class::primaryKey();
244
245 24
        if (count($pks) > 1) {
246
            // composite primary key
247 6
            foreach ($models as $i => $model) {
248 6
                $key = [];
249 6
                foreach ($pks as $pk) {
250 6
                    if (!isset($model[$pk])) {
251
                        // do not continue if the primary key is not part of the result set
252 3
                        break 2;
253
                    }
254 6
                    $key[] = $model[$pk];
255 6
                }
256 3
                $key = serialize($key);
257 3
                if (isset($hash[$key])) {
258
                    unset($models[$i]);
259
                } else {
260 3
                    $hash[$key] = true;
261
                }
262 6
            }
263 24
        } elseif (empty($pks)) {
264
            throw new InvalidConfigException("Primary key of '{$class}' can not be empty.");
265
        } else {
266
            // single column primary key
267 21
            $pk = reset($pks);
268 21
            foreach ($models as $i => $model) {
269 21
                if (!isset($model[$pk])) {
270
                    // do not continue if the primary key is not part of the result set
271 3
                    break;
272
                }
273 18
                $key = $model[$pk];
274 18
                if (isset($hash[$key])) {
275 12
                    unset($models[$i]);
276 18
                } elseif ($key !== null) {
277 18
                    $hash[$key] = true;
278 18
                }
279 21
            }
280
        }
281
282 24
        return array_values($models);
283
    }
284
285
    /**
286
     * Executes query and returns a single row of result.
287
     * @param Connection $db the DB connection used to create the DB command.
288
     * If null, the DB connection returned by [[modelClass]] will be used.
289
     * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
290
     * the query result may be either an array or an ActiveRecord object. Null will be returned
291
     * if the query results in nothing.
292
     */
293 209
    public function one($db = null)
294
    {
295 209
        $row = parent::one($db);
296 209
        if ($row !== false) {
297 209
            $models = $this->populate([$row]);
298 209
            return reset($models) ?: null;
299
        } else {
300 21
            return null;
301
        }
302
    }
303
304
    /**
305
     * Creates a DB command that can be used to execute this query.
306
     * @param Connection $db the DB connection used to create the DB command.
307
     * If null, the DB connection returned by [[modelClass]] will be used.
308
     * @return Command the created DB command instance.
309
     */
310 293
    public function createCommand($db = null)
311
    {
312
        /* @var $modelClass ActiveRecord */
313 293
        $modelClass = $this->modelClass;
314 293
        if ($db === null) {
315 291
            $db = $modelClass::getDb();
316 291
        }
317
318 293
        if ($this->sql === null) {
319 290
            list ($sql, $params) = $db->getQueryBuilder()->build($this);
320 290
        } else {
321 3
            $sql = $this->sql;
322 3
            $params = $this->params;
323
        }
324
325 293
        return $db->createCommand($sql, $params);
326
    }
327
328
    /**
329
     * @inheritdoc
330
     */
331 55
    protected function queryScalar($selectExpression, $db)
332
    {
333 55
        if ($this->sql === null) {
334 52
            return parent::queryScalar($selectExpression, $db);
335
        }
336
        /* @var $modelClass ActiveRecord */
337 3
        $modelClass = $this->modelClass;
338 3
        if ($db === null) {
339 3
            $db = $modelClass::getDb();
340 3
        }
341 3
        return (new Query)->select([$selectExpression])
342 3
            ->from(['c' => "({$this->sql})"])
343 3
            ->params($this->params)
344 3
            ->createCommand($db)
345 3
            ->queryScalar();
346
    }
347
348
    /**
349
     * Joins with the specified relations.
350
     *
351
     * This method allows you to reuse existing relation definitions to perform JOIN queries.
352
     * Based on the definition of the specified relation(s), the method will append one or multiple
353
     * JOIN statements to the current query.
354
     *
355
     * If the `$eagerLoading` parameter is true, the method will also perform eager loading for the specified relations,
356
     * which is equivalent to calling [[with()]] using the specified relations.
357
     *
358
     * Note that because a JOIN query will be performed, you are responsible to disambiguate column names.
359
     *
360
     * This method differs from [[with()]] in that it will build up and execute a JOIN SQL statement
361
     * for the primary table. And when `$eagerLoading` is true, it will call [[with()]] in addition with the specified relations.
362
     *
363
     * @param string|array $with the relations to be joined. This can either be a string, representing a relation name or
364
     * an array with the following semantics:
365
     *
366
     * - Each array element represents a single relation.
367
     * - You may specify the relation name as the array key and provide an anonymous functions that
368
     *   can be used to modify the relation queries on-the-fly as the array value.
369
     * - If a relation query does not need modification, you may use the relation name as the array value.
370
     *
371
     * The relation name may optionally contain an alias for the relation table (e.g. `books b`).
372
     *
373
     * Sub-relations can also be specified, see [[with()]] for the syntax.
374
     *
375
     * In the following you find some examples:
376
     *
377
     * ```php
378
     * // find all orders that contain books, and eager loading "books"
379
     * Order::find()->joinWith('books', true, 'INNER JOIN')->all();
380
     * // find all orders, eager loading "books", and sort the orders and books by the book names.
381
     * Order::find()->joinWith([
382
     *     'books' => function (\yii\db\ActiveQuery $query) {
383
     *         $query->orderBy('item.name');
384
     *     }
385
     * ])->all();
386
     * // find all orders that contain books of the category 'Science fiction', using the alias "b" for the books table
387
     * Order::find()->joinWith(['books b'], true, 'INNER JOIN')->where(['b.category' => 'Science fiction'])->all();
388
     * ```
389
     *
390
     * The alias syntax is available since version 2.0.7.
391
     *
392
     * @param boolean|array $eagerLoading whether to eager load the relations specified in `$with`.
393
     * When this is a boolean, it applies to all relations specified in `$with`. Use an array
394
     * to explicitly list which relations in `$with` need to be eagerly loaded. Defaults to `true`.
395
     * @param string|array $joinType the join type of the relations specified in `$with`.
396
     * When this is a string, it applies to all relations specified in `$with`. Use an array
397
     * in the format of `relationName => joinType` to specify different join types for different relations.
398
     * @return $this the query object itself
399
     */
400 42
    public function joinWith($with, $eagerLoading = true, $joinType = 'LEFT JOIN')
401
    {
402 42
        $relations = [];
403 42
        foreach ((array) $with as $name => $callback) {
404 42
            if (is_int($name)) {
405 42
                $name = $callback;
406 42
                $callback = null;
407 42
            }
408
409 42
            if (preg_match('/^(.*?)(?:\s+AS\s+|\s+)(\w+)$/i', $name, $matches)) {
410
                // relation is defined with an alias, adjust callback to apply alias
411 9
                list(, $relation, $alias) = $matches;
412 9
                $name = $relation;
413 9
                $callback = function ($query) use ($callback, $alias) {
414
                    /** @var $query ActiveQuery */
415 9
                    $query->alias($alias);
416 9
                    if ($callback !== null) {
417 9
                        call_user_func($callback, $query);
418 9
                    }
419 9
                };
420 9
            }
421
422 42
            if ($callback === null) {
423 42
                $relations[] = $name;
424 42
            } else {
425 15
                $relations[$name] = $callback;
426
            }
427 42
        }
428 42
        $this->joinWith[] = [$relations, $eagerLoading, $joinType];
429 42
        return $this;
430
    }
431
432 36
    private function buildJoinWith()
433
    {
434 36
        $join = $this->join;
435 36
        $this->join = [];
436
437 36
        $model = new $this->modelClass;
438 36
        foreach ($this->joinWith as $config) {
439 36
            list ($with, $eagerLoading, $joinType) = $config;
440 36
            $this->joinWithRelations($model, $with, $joinType);
441
442 36
            if (is_array($eagerLoading)) {
443
                foreach ($with as $name => $callback) {
444
                    if (is_int($name)) {
445
                        if (!in_array($callback, $eagerLoading, true)) {
446
                            unset($with[$name]);
447
                        }
448
                    } elseif (!in_array($name, $eagerLoading, true)) {
449
                        unset($with[$name]);
450
                    }
451
                }
452 36
            } elseif (!$eagerLoading) {
453 12
                $with = [];
454 12
            }
455
456 36
            $this->with($with);
457 36
        }
458
459
        // remove duplicated joins added by joinWithRelations that may be added
460
        // e.g. when joining a relation and a via relation at the same time
461 36
        $uniqueJoins = [];
462 36
        foreach ($this->join as $j) {
463 36
            $uniqueJoins[serialize($j)] = $j;
464 36
        }
465 36
        $this->join = array_values($uniqueJoins);
466
467 36
        if (!empty($join)) {
468
            // append explicit join to joinWith()
469
            // https://github.com/yiisoft/yii2/issues/2880
470
            $this->join = empty($this->join) ? $join : array_merge($this->join, $join);
471
        }
472 36
    }
473
474
    /**
475
     * Inner joins with the specified relations.
476
     * This is a shortcut method to [[joinWith()]] with the join type set as "INNER JOIN".
477
     * Please refer to [[joinWith()]] for detailed usage of this method.
478
     * @param string|array $with the relations to be joined with.
479
     * @param boolean|array $eagerLoading whether to eager loading the relations.
480
     * @return $this the query object itself
481
     * @see joinWith()
482
     */
483 12
    public function innerJoinWith($with, $eagerLoading = true)
484
    {
485 12
        return $this->joinWith($with, $eagerLoading, 'INNER JOIN');
486
    }
487
488
    /**
489
     * Modifies the current query by adding join fragments based on the given relations.
490
     * @param ActiveRecord $model the primary model
491
     * @param array $with the relations to be joined
492
     * @param string|array $joinType the join type
493
     */
494 36
    private function joinWithRelations($model, $with, $joinType)
495
    {
496 36
        $relations = [];
497
498 36
        foreach ($with as $name => $callback) {
499 36
            if (is_int($name)) {
500 36
                $name = $callback;
501 36
                $callback = null;
502 36
            }
503
504 36
            $primaryModel = $model;
505 36
            $parent = $this;
506 36
            $prefix = '';
507 36
            while (($pos = strpos($name, '.')) !== false) {
508 6
                $childName = substr($name, $pos + 1);
509 6
                $name = substr($name, 0, $pos);
510 6
                $fullName = $prefix === '' ? $name : "$prefix.$name";
511 6
                if (!isset($relations[$fullName])) {
512
                    $relations[$fullName] = $relation = $primaryModel->getRelation($name);
513
                    $this->joinWithRelation($parent, $relation, $this->getJoinType($joinType, $fullName));
514
                } else {
515 6
                    $relation = $relations[$fullName];
516
                }
517 6
                $primaryModel = new $relation->modelClass;
518 6
                $parent = $relation;
519 6
                $prefix = $fullName;
520 6
                $name = $childName;
521 6
            }
522
523 36
            $fullName = $prefix === '' ? $name : "$prefix.$name";
524 36
            if (!isset($relations[$fullName])) {
525 36
                $relations[$fullName] = $relation = $primaryModel->getRelation($name);
526 36
                if ($callback !== null) {
527 15
                    call_user_func($callback, $relation);
528 15
                }
529 36
                if (!empty($relation->joinWith)) {
530 6
                    $relation->buildJoinWith();
531 6
                }
532 36
                $this->joinWithRelation($parent, $relation, $this->getJoinType($joinType, $fullName));
533 36
            }
534 36
        }
535 36
    }
536
537
    /**
538
     * Returns the join type based on the given join type parameter and the relation name.
539
     * @param string|array $joinType the given join type(s)
540
     * @param string $name relation name
541
     * @return string the real join type
542
     */
543 36
    private function getJoinType($joinType, $name)
544
    {
545 36
        if (is_array($joinType) && isset($joinType[$name])) {
546
            return $joinType[$name];
547
        } else {
548 36
            return is_string($joinType) ? $joinType : 'INNER JOIN';
549
        }
550
    }
551
552
    /**
553
     * Returns the table name and the table alias for [[modelClass]].
554
     * @param ActiveQuery $query
555
     * @return array the table name and the table alias.
556
     */
557 45
    private function getQueryTableName($query)
558
    {
559 45
        if (empty($query->from)) {
560
            /* @var $modelClass ActiveRecord */
561 42
            $modelClass = $query->modelClass;
562 42
            $tableName = $modelClass::tableName();
563 42
        } else {
564 39
            $tableName = '';
565 39
            foreach ($query->from as $alias => $tableName) {
566 39
                if (is_string($alias)) {
567 12
                    return [$tableName, $alias];
568
                } else {
569 36
                    break;
570
                }
571 36
            }
572
        }
573
574 45
        if (preg_match('/^(.*?)\s+({{\w+}}|\w+)$/', $tableName, $matches)) {
575 3
            $alias = $matches[2];
576 3
        } else {
577 45
            $alias = $tableName;
578
        }
579
580 45
        return [$tableName, $alias];
581
    }
582
583
    /**
584
     * Joins a parent query with a child query.
585
     * The current query object will be modified accordingly.
586
     * @param ActiveQuery $parent
587
     * @param ActiveQuery $child
588
     * @param string $joinType
589
     */
590 36
    private function joinWithRelation($parent, $child, $joinType)
591
    {
592 36
        $via = $child->via;
593 36
        $child->via = null;
594 36
        if ($via instanceof ActiveQuery) {
595
            // via table
596 9
            $this->joinWithRelation($parent, $via, $joinType);
597 9
            $this->joinWithRelation($via, $child, $joinType);
598 9
            return;
599 36
        } elseif (is_array($via)) {
600
            // via relation
601 12
            $this->joinWithRelation($parent, $via[1], $joinType);
602 12
            $this->joinWithRelation($via[1], $child, $joinType);
603 12
            return;
604
        }
605
606 36
        list ($parentTable, $parentAlias) = $this->getQueryTableName($parent);
0 ignored issues
show
Unused Code introduced by
The assignment to $parentTable 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...
607 36
        list ($childTable, $childAlias) = $this->getQueryTableName($child);
608
609 36
        if (!empty($child->link)) {
610
611 36
            if (strpos($parentAlias, '{{') === false) {
612 30
                $parentAlias = '{{' . $parentAlias . '}}';
613 30
            }
614 36
            if (strpos($childAlias, '{{') === false) {
615 36
                $childAlias = '{{' . $childAlias . '}}';
616 36
            }
617
618 36
            $on = [];
619 36
            foreach ($child->link as $childColumn => $parentColumn) {
620 36
                $on[] = "$parentAlias.[[$parentColumn]] = $childAlias.[[$childColumn]]";
621 36
            }
622 36
            $on = implode(' AND ', $on);
623 36
            if (!empty($child->on)) {
624 9
                $on = ['and', $on, $child->on];
625 9
            }
626 36
        } else {
627
            $on = $child->on;
628
        }
629 36
        $this->join($joinType, empty($child->from) ? $childTable : $child->from, $on);
630
631 36
        if (!empty($child->where)) {
632 6
            $this->andWhere($child->where);
633 6
        }
634 36
        if (!empty($child->having)) {
635
            $this->andHaving($child->having);
636
        }
637 36
        if (!empty($child->orderBy)) {
638 12
            $this->addOrderBy($child->orderBy);
639 12
        }
640 36
        if (!empty($child->groupBy)) {
641
            $this->addGroupBy($child->groupBy);
642
        }
643 36
        if (!empty($child->params)) {
644
            $this->addParams($child->params);
645
        }
646 36
        if (!empty($child->join)) {
647 6
            foreach ($child->join as $join) {
648 6
                $this->join[] = $join;
649 6
            }
650 6
        }
651 36
        if (!empty($child->union)) {
652
            foreach ($child->union as $union) {
653
                $this->union[] = $union;
654
            }
655
        }
656 36
    }
657
658
    /**
659
     * Sets the ON condition for a relational query.
660
     * The condition will be used in the ON part when [[ActiveQuery::joinWith()]] is called.
661
     * Otherwise, the condition will be used in the WHERE part of a query.
662
     *
663
     * Use this method to specify additional conditions when declaring a relation in the [[ActiveRecord]] class:
664
     *
665
     * ```php
666
     * public function getActiveUsers()
667
     * {
668
     *     return $this->hasMany(User::className(), ['id' => 'user_id'])
669
     *                 ->onCondition(['active' => true]);
670
     * }
671
     * ```
672
     *
673
     * Note that this condition is applied in case of a join as well as when fetching the related records.
674
     * Thus only fields of the related table can be used in the condition. Trying to access fields of the primary
675
     * record will cause an error in a non-join-query.
676
     *
677
     * @param string|array $condition the ON condition. Please refer to [[Query::where()]] on how to specify this parameter.
678
     * @param array $params the parameters (name => value) to be bound to the query.
679
     * @return $this the query object itself
680
     */
681 15
    public function onCondition($condition, $params = [])
682
    {
683 15
        $this->on = $condition;
684 15
        $this->addParams($params);
685 15
        return $this;
686
    }
687
688
    /**
689
     * Adds an additional ON condition to the existing one.
690
     * The new condition and the existing one will be joined using the 'AND' operator.
691
     * @param string|array $condition the new ON condition. Please refer to [[where()]]
692
     * on how to specify this parameter.
693
     * @param array $params the parameters (name => value) to be bound to the query.
694
     * @return $this the query object itself
695
     * @see onCondition()
696
     * @see orOnCondition()
697
     */
698 6
    public function andOnCondition($condition, $params = [])
699
    {
700 6
        if ($this->on === null) {
701 3
            $this->on = $condition;
702 3
        } else {
703 3
            $this->on = ['and', $this->on, $condition];
704
        }
705 6
        $this->addParams($params);
706 6
        return $this;
707
    }
708
709
    /**
710
     * Adds an additional ON condition to the existing one.
711
     * The new condition and the existing one will be joined using the 'OR' operator.
712
     * @param string|array $condition the new ON condition. Please refer to [[where()]]
713
     * on how to specify this parameter.
714
     * @param array $params the parameters (name => value) to be bound to the query.
715
     * @return $this the query object itself
716
     * @see onCondition()
717
     * @see andOnCondition()
718
     */
719 6
    public function orOnCondition($condition, $params = [])
720
    {
721 6
        if ($this->on === null) {
722 3
            $this->on = $condition;
723 3
        } else {
724 3
            $this->on = ['or', $this->on, $condition];
725
        }
726 6
        $this->addParams($params);
727 6
        return $this;
728
    }
729
730
    /**
731
     * Specifies the junction table for a relational query.
732
     *
733
     * Use this method to specify a junction table when declaring a relation in the [[ActiveRecord]] class:
734
     *
735
     * ```php
736
     * public function getItems()
737
     * {
738
     *     return $this->hasMany(Item::className(), ['id' => 'item_id'])
739
     *                 ->viaTable('order_item', ['order_id' => 'id']);
740
     * }
741
     * ```
742
     *
743
     * @param string $tableName the name of the junction table.
744
     * @param array $link the link between the junction table and the table associated with [[primaryModel]].
745
     * The keys of the array represent the columns in the junction table, and the values represent the columns
746
     * in the [[primaryModel]] table.
747
     * @param callable $callable a PHP callback for customizing the relation associated with the junction table.
748
     * Its signature should be `function($query)`, where `$query` is the query to be customized.
749
     * @return $this the query object itself
750
     * @see via()
751
     */
752 24
    public function viaTable($tableName, $link, callable $callable = null)
753
    {
754 24
        $relation = new ActiveQuery(get_class($this->primaryModel), [
755 24
            'from' => [$tableName],
756 24
            'link' => $link,
757 24
            'multiple' => true,
758 24
            'asArray' => true,
759 24
        ]);
760 24
        $this->via = $relation;
761 24
        if ($callable !== null) {
762 6
            call_user_func($callable, $relation);
763 6
        }
764
765 24
        return $this;
766
    }
767
768
    /**
769
     * Define an alias for the table defined in [[modelClass]].
770
     *
771
     * This method will adjust [[from]] so that an already defined alias will be overwritten.
772
     * If none was defined, [[from]] will be populated with the given alias.
773
     *
774
     * @param string $alias the table alias.
775
     * @return $this the query object itself
776
     * @since 2.0.7
777
     */
778 18
    public function alias($alias)
779
    {
780 18
        if (empty($this->from) || count($this->from) < 2) {
781 18
            list($tableName, ) = $this->getQueryTableName($this);
782 18
            $this->from = [$alias => $tableName];
783 18
        } else {
784
            /* @var $modelClass ActiveRecord */
785 3
            $modelClass = $this->modelClass;
786 3
            $tableName = $modelClass::tableName();
787
788 3
            foreach ($this->from as $key => $table) {
789 3
                if ($table === $tableName) {
790 3
                    unset($this->from[$key]);
791 3
                    $this->from[$alias] = $tableName;
792 3
                }
793 3
            }
794
        }
795 18
        return $this;
796
    }
797
}
798