Completed
Push — cache-closure ( 7d9053...380edd )
by Dmitry
35:54
created

BaseActiveRecord   F

Complexity

Total Complexity 223

Size/Duplication

Total Lines 1609
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 70.3%

Importance

Changes 0
Metric Value
wmc 223
lcom 1
cbo 14
dl 0
loc 1609
ccs 400
cts 569
cp 0.703
rs 1.913
c 0
b 0
f 0

61 Methods

Rating   Name   Duplication   Size   Complexity  
A findOne() 0 4 1
A findAll() 0 4 1
A findByCondition() 0 16 3
A updateAll() 0 4 1
A updateAllCounters() 0 4 1
A deleteAll() 0 4 1
A optimisticLock() 0 4 1
B __get() 0 18 7
A __isset() 0 8 2
A __unset() 0 10 4
A hasOne() 0 10 1
A hasMany() 0 10 1
A __set() 0 8 2
A canGetProperty() 0 13 3
A canSetProperty() 0 13 3
A populateRelation() 0 4 1
A isRelationPopulated() 0 4 1
A getRelatedRecords() 0 4 1
A hasAttribute() 0 4 2
A getAttribute() 0 4 2
A setAttribute() 0 8 2
A getOldAttributes() 0 4 2
A setOldAttributes() 0 4 1
A getOldAttribute() 0 4 2
A setOldAttribute() 0 8 3
A markAttributeDirty() 0 4 1
A isAttributeChanged() 0 12 4
C getDirtyAttributes() 0 22 9
A save() 0 8 2
A update() 0 7 3
B updateAttributes() 0 25 6
D updateInternal() 0 37 9
A updateCounters() 0 16 4
B delete() 0 21 5
A getIsNewRecord() 0 4 1
A setIsNewRecord() 0 4 2
A init() 0 5 1
A afterFind() 0 4 1
A beforeSave() 0 7 2
D unlink() 0 83 21
A afterSave() 0 6 2
A beforeDelete() 0 7 1
A afterDelete() 0 4 1
A refresh() 0 16 4
A afterRefresh() 0 4 1
A equals() 0 8 4
B getPrimaryKey() 0 14 6
B getOldPrimaryKey() 0 17 7
A populateRecord() 0 12 4
A instantiate() 0 4 1
A offsetExists() 0 4 1
C getRelation() 0 36 8
C link() 0 77 21
C unlinkAll() 0 73 16
A bindModels() 0 15 4
A isPrimaryKey() 0 9 2
C getAttributeLabel() 0 31 8
C getAttributeHint() 0 30 8
A fields() 0 6 1
A extraFields() 0 6 1
A offsetUnset() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like BaseActiveRecord 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 BaseActiveRecord, 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
use yii\base\Event;
12
use yii\base\Model;
13
use yii\base\InvalidParamException;
14
use yii\base\ModelEvent;
15
use yii\base\NotSupportedException;
16
use yii\base\UnknownMethodException;
17
use yii\base\InvalidCallException;
18
use yii\helpers\ArrayHelper;
19
20
/**
21
 * ActiveRecord is the base class for classes representing relational data in terms of objects.
22
 *
23
 * See [[\yii\db\ActiveRecord]] for a concrete implementation.
24
 *
25
 * @property array $dirtyAttributes The changed attribute values (name-value pairs). This property is
26
 * read-only.
27
 * @property bool $isNewRecord Whether the record is new and should be inserted when calling [[save()]].
28
 * @property array $oldAttributes The old attribute values (name-value pairs). Note that the type of this
29
 * property differs in getter and setter. See [[getOldAttributes()]] and [[setOldAttributes()]] for details.
30
 * @property mixed $oldPrimaryKey The old primary key value. An array (column name => column value) is
31
 * returned if the primary key is composite. A string is returned otherwise (null will be returned if the key
32
 * value is null). This property is read-only.
33
 * @property mixed $primaryKey The primary key value. An array (column name => column value) is returned if
34
 * the primary key is composite. A string is returned otherwise (null will be returned if the key value is null).
35
 * This property is read-only.
36
 * @property array $relatedRecords An array of related records indexed by relation names. This property is
37
 * read-only.
38
 *
39
 * @author Qiang Xue <[email protected]>
40
 * @author Carsten Brandt <[email protected]>
41
 * @since 2.0
42
 */
43
abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
44
{
45
    /**
46
     * @event Event an event that is triggered when the record is initialized via [[init()]].
47
     */
48
    const EVENT_INIT = 'init';
49
    /**
50
     * @event Event an event that is triggered after the record is created and populated with query result.
51
     */
52
    const EVENT_AFTER_FIND = 'afterFind';
53
    /**
54
     * @event ModelEvent an event that is triggered before inserting a record.
55
     * You may set [[ModelEvent::isValid]] to be `false` to stop the insertion.
56
     */
57
    const EVENT_BEFORE_INSERT = 'beforeInsert';
58
    /**
59
     * @event AfterSaveEvent an event that is triggered after a record is inserted.
60
     */
61
    const EVENT_AFTER_INSERT = 'afterInsert';
62
    /**
63
     * @event ModelEvent an event that is triggered before updating a record.
64
     * You may set [[ModelEvent::isValid]] to be `false` to stop the update.
65
     */
66
    const EVENT_BEFORE_UPDATE = 'beforeUpdate';
67
    /**
68
     * @event AfterSaveEvent an event that is triggered after a record is updated.
69
     */
70
    const EVENT_AFTER_UPDATE = 'afterUpdate';
71
    /**
72
     * @event ModelEvent an event that is triggered before deleting a record.
73
     * You may set [[ModelEvent::isValid]] to be `false` to stop the deletion.
74
     */
75
    const EVENT_BEFORE_DELETE = 'beforeDelete';
76
    /**
77
     * @event Event an event that is triggered after a record is deleted.
78
     */
79
    const EVENT_AFTER_DELETE = 'afterDelete';
80
    /**
81
     * @event Event an event that is triggered after a record is refreshed.
82
     * @since 2.0.8
83
     */
84
    const EVENT_AFTER_REFRESH = 'afterRefresh';
85
86
    /**
87
     * @var array attribute values indexed by attribute names
88
     */
89
    private $_attributes = [];
90
    /**
91
     * @var array|null old attribute values indexed by attribute names.
92
     * This is `null` if the record [[isNewRecord|is new]].
93
     */
94
    private $_oldAttributes;
95
    /**
96
     * @var array related models indexed by the relation names
97
     */
98
    private $_related = [];
99
100
101
    /**
102
     * @inheritdoc
103
     * @return static ActiveRecord instance matching the condition, or `null` if nothing matches.
104
     */
105 161
    public static function findOne($condition)
106
    {
107 161
        return static::findByCondition($condition)->one();
0 ignored issues
show
Bug Compatibility introduced by
The expression static::findByCondition($condition)->one(); of type yii\db\ActiveRecordInterface|array|null adds the type array to the return on line 107 which is incompatible with the return type declared by the interface yii\db\ActiveRecordInterface::findOne of type yii\db\ActiveRecordInterface.
Loading history...
108
    }
109
110
    /**
111
     * @inheritdoc
112
     * @return static[] an array of ActiveRecord instances, or an empty array if nothing matches.
113
     */
114
    public static function findAll($condition)
115
    {
116
        return static::findByCondition($condition)->all();
117
    }
118
119
    /**
120
     * Finds ActiveRecord instance(s) by the given condition.
121
     * This method is internally called by [[findOne()]] and [[findAll()]].
122
     * @param mixed $condition please refer to [[findOne()]] for the explanation of this parameter
123
     * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance.
124
     * @throws InvalidConfigException if there is no primary key defined
125
     * @internal
126
     */
127
    protected static function findByCondition($condition)
128
    {
129
        $query = static::find();
130
131
        if (!ArrayHelper::isAssociative($condition)) {
132
            // query by primary key
133
            $primaryKey = static::primaryKey();
134
            if (isset($primaryKey[0])) {
135
                $condition = [$primaryKey[0] => $condition];
136
            } else {
137
                throw new InvalidConfigException('"' . get_called_class() . '" must have a primary key.');
138
            }
139
        }
140
141
        return $query->andWhere($condition);
142
    }
143
144
    /**
145
     * Updates the whole table using the provided attribute values and conditions.
146
     * For example, to change the status to be 1 for all customers whose status is 2:
147
     *
148
     * ```php
149
     * Customer::updateAll(['status' => 1], 'status = 2');
150
     * ```
151
     *
152
     * @param array $attributes attribute values (name-value pairs) to be saved into the table
153
     * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
154
     * Please refer to [[Query::where()]] on how to specify this parameter.
155
     * @return int the number of rows updated
156
     * @throws NotSupportedException if not overridden
157
     */
158
    public static function updateAll($attributes, $condition = '')
159
    {
160
        throw new NotSupportedException(__METHOD__ . ' is not supported.');
161
    }
162
163
    /**
164
     * Updates the whole table using the provided counter changes and conditions.
165
     * For example, to increment all customers' age by 1,
166
     *
167
     * ```php
168
     * Customer::updateAllCounters(['age' => 1]);
169
     * ```
170
     *
171
     * @param array $counters the counters to be updated (attribute name => increment value).
172
     * Use negative values if you want to decrement the counters.
173
     * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
174
     * Please refer to [[Query::where()]] on how to specify this parameter.
175
     * @return int the number of rows updated
176
     * @throws NotSupportedException if not overrided
177
     */
178
    public static function updateAllCounters($counters, $condition = '')
179
    {
180
        throw new NotSupportedException(__METHOD__ . ' is not supported.');
181
    }
182
183
    /**
184
     * Deletes rows in the table using the provided conditions.
185
     * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
186
     *
187
     * For example, to delete all customers whose status is 3:
188
     *
189
     * ```php
190
     * Customer::deleteAll('status = 3');
191
     * ```
192
     *
193
     * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
194
     * Please refer to [[Query::where()]] on how to specify this parameter.
195
     * @param array $params the parameters (name => value) to be bound to the query.
196
     * @return int the number of rows deleted
197
     * @throws NotSupportedException if not overrided
198
     */
199
    public static function deleteAll($condition = '', $params = [])
200
    {
201
        throw new NotSupportedException(__METHOD__ . ' is not supported.');
202
    }
203
204
    /**
205
     * Returns the name of the column that stores the lock version for implementing optimistic locking.
206
     *
207
     * Optimistic locking allows multiple users to access the same record for edits and avoids
208
     * potential conflicts. In case when a user attempts to save the record upon some staled data
209
     * (because another user has modified the data), a [[StaleObjectException]] exception will be thrown,
210
     * and the update or deletion is skipped.
211
     *
212
     * Optimistic locking is only supported by [[update()]] and [[delete()]].
213
     *
214
     * To use Optimistic locking:
215
     *
216
     * 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`.
217
     *    Override this method to return the name of this column.
218
     * 2. Add a `required` validation rule for the version column to ensure the version value is submitted.
219
     * 3. In the Web form that collects the user input, add a hidden field that stores
220
     *    the lock version of the recording being updated.
221
     * 4. In the controller action that does the data updating, try to catch the [[StaleObjectException]]
222
     *    and implement necessary business logic (e.g. merging the changes, prompting stated data)
223
     *    to resolve the conflict.
224
     *
225
     * @return string the column name that stores the lock version of a table row.
226
     * If `null` is returned (default implemented), optimistic locking will not be supported.
227
     */
228 23
    public function optimisticLock()
229
    {
230 23
        return null;
231
    }
232
233
    /**
234
     * @inheritdoc
235
     */
236 3
    public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
237
    {
238 3
        if (parent::canGetProperty($name, $checkVars, $checkBehaviors)) {
239 3
            return true;
240
        }
241
242
        try {
243 3
            return $this->hasAttribute($name);
244
        } catch (\Exception $e) {
245
            // `hasAttribute()` may fail on base/abstract classes in case automatic attribute list fetching used
246
            return false;
247
        }
248
    }
249
250
    /**
251
     * @inheritdoc
252
     */
253 9
    public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
254
    {
255 9
        if (parent::canSetProperty($name, $checkVars, $checkBehaviors)) {
256 6
            return true;
257
        }
258
259
        try {
260 3
            return $this->hasAttribute($name);
261
        } catch (\Exception $e) {
262
            // `hasAttribute()` may fail on base/abstract classes in case automatic attribute list fetching used
263
            return false;
264
        }
265
    }
266
267
    /**
268
     * PHP getter magic method.
269
     * This method is overridden so that attributes and related objects can be accessed like properties.
270
     *
271
     * @param string $name property name
272
     * @throws \yii\base\InvalidParamException if relation name is wrong
273
     * @return mixed property value
274
     * @see getAttribute()
275
     */
276 273
    public function __get($name)
277
    {
278 273
        if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
279 262
            return $this->_attributes[$name];
280 146
        } elseif ($this->hasAttribute($name)) {
281 30
            return null;
282
        } else {
283 128
            if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {
284 78
                return $this->_related[$name];
285
            }
286 83
            $value = parent::__get($name);
287 83
            if ($value instanceof ActiveQueryInterface) {
288 52
                return $this->_related[$name] = $value->findFor($name, $this);
289
            } else {
290 37
                return $value;
291
            }
292
        }
293
    }
294
295
    /**
296
     * PHP setter magic method.
297
     * This method is overridden so that AR attributes can be accessed like properties.
298
     * @param string $name property name
299
     * @param mixed $value property value
300
     */
301 123
    public function __set($name, $value)
302
    {
303 123
        if ($this->hasAttribute($name)) {
304 123
            $this->_attributes[$name] = $value;
305 123
        } else {
306 3
            parent::__set($name, $value);
307
        }
308 123
    }
309
310
    /**
311
     * Checks if a property value is null.
312
     * This method overrides the parent implementation by checking if the named attribute is `null` or not.
313
     * @param string $name the property name or the event name
314
     * @return bool whether the property value is null
315
     */
316 39
    public function __isset($name)
317
    {
318
        try {
319 39
            return $this->__get($name) !== null;
320
        } catch (\Exception $e) {
321
            return false;
322
        }
323
    }
324
325
    /**
326
     * Sets a component property to be null.
327
     * This method overrides the parent implementation by clearing
328
     * the specified attribute value.
329
     * @param string $name the property name or the event name
330
     */
331 9
    public function __unset($name)
332
    {
333 9
        if ($this->hasAttribute($name)) {
334 3
            unset($this->_attributes[$name]);
335 9
        } elseif (array_key_exists($name, $this->_related)) {
336 6
            unset($this->_related[$name]);
337 6
        } elseif ($this->getRelation($name, false) === null) {
338
            parent::__unset($name);
339
        }
340 9
    }
341
342
    /**
343
     * Declares a `has-one` relation.
344
     * The declaration is returned in terms of a relational [[ActiveQuery]] instance
345
     * through which the related record can be queried and retrieved back.
346
     *
347
     * A `has-one` relation means that there is at most one related record matching
348
     * the criteria set by this relation, e.g., a customer has one country.
349
     *
350
     * For example, to declare the `country` relation for `Customer` class, we can write
351
     * the following code in the `Customer` class:
352
     *
353
     * ```php
354
     * public function getCountry()
355
     * {
356
     *     return $this->hasOne(Country::className(), ['id' => 'country_id']);
357
     * }
358
     * ```
359
     *
360
     * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name
361
     * in the related class `Country`, while the 'country_id' value refers to an attribute name
362
     * in the current AR class.
363
     *
364
     * Call methods declared in [[ActiveQuery]] to further customize the relation.
365
     *
366
     * @param string $class the class name of the related record
367
     * @param array $link the primary-foreign key constraint. The keys of the array refer to
368
     * the attributes of the record associated with the `$class` model, while the values of the
369
     * array refer to the corresponding attributes in **this** AR class.
370
     * @return ActiveQueryInterface the relational query object.
371
     */
372 43
    public function hasOne($class, $link)
373
    {
374
        /* @var $class ActiveRecordInterface */
375
        /* @var $query ActiveQuery */
376 43
        $query = $class::find();
377 43
        $query->primaryModel = $this;
0 ignored issues
show
Documentation Bug introduced by
$this is of type object<yii\db\BaseActiveRecord>, but the property $primaryModel was declared to be of type object<yii\db\ActiveRecord>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
378 43
        $query->link = $link;
379 43
        $query->multiple = false;
380 43
        return $query;
381
    }
382
383
    /**
384
     * Declares a `has-many` relation.
385
     * The declaration is returned in terms of a relational [[ActiveQuery]] instance
386
     * through which the related record can be queried and retrieved back.
387
     *
388
     * A `has-many` relation means that there are multiple related records matching
389
     * the criteria set by this relation, e.g., a customer has many orders.
390
     *
391
     * For example, to declare the `orders` relation for `Customer` class, we can write
392
     * the following code in the `Customer` class:
393
     *
394
     * ```php
395
     * public function getOrders()
396
     * {
397
     *     return $this->hasMany(Order::className(), ['customer_id' => 'id']);
398
     * }
399
     * ```
400
     *
401
     * Note that in the above, the 'customer_id' key in the `$link` parameter refers to
402
     * an attribute name in the related class `Order`, while the 'id' value refers to
403
     * an attribute name in the current AR class.
404
     *
405
     * Call methods declared in [[ActiveQuery]] to further customize the relation.
406
     *
407
     * @param string $class the class name of the related record
408
     * @param array $link the primary-foreign key constraint. The keys of the array refer to
409
     * the attributes of the record associated with the `$class` model, while the values of the
410
     * array refer to the corresponding attributes in **this** AR class.
411
     * @return ActiveQueryInterface the relational query object.
412
     */
413 129
    public function hasMany($class, $link)
414
    {
415
        /* @var $class ActiveRecordInterface */
416
        /* @var $query ActiveQuery */
417 129
        $query = $class::find();
418 129
        $query->primaryModel = $this;
0 ignored issues
show
Documentation Bug introduced by
$this is of type object<yii\db\BaseActiveRecord>, but the property $primaryModel was declared to be of type object<yii\db\ActiveRecord>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
419 129
        $query->link = $link;
420 129
        $query->multiple = true;
421 129
        return $query;
422
    }
423
424
    /**
425
     * Populates the named relation with the related records.
426
     * Note that this method does not check if the relation exists or not.
427
     * @param string $name the relation name, e.g. `orders` for a relation defined via `getOrders()` method (case-sensitive).
428
     * @param ActiveRecordInterface|array|null $records the related records to be populated into the relation.
429
     * @see getRelation()
430
     */
431 96
    public function populateRelation($name, $records)
432
    {
433 96
        $this->_related[$name] = $records;
434 96
    }
435
436
    /**
437
     * Check whether the named relation has been populated with records.
438
     * @param string $name the relation name, e.g. `orders` for a relation defined via `getOrders()` method (case-sensitive).
439
     * @return bool whether relation has been populated with records.
440
     * @see getRelation()
441
     */
442 45
    public function isRelationPopulated($name)
443
    {
444 45
        return array_key_exists($name, $this->_related);
445
    }
446
447
    /**
448
     * Returns all populated related records.
449
     * @return array an array of related records indexed by relation names.
450
     * @see getRelation()
451
     */
452 6
    public function getRelatedRecords()
453
    {
454 6
        return $this->_related;
455
    }
456
457
    /**
458
     * Returns a value indicating whether the model has an attribute with the specified name.
459
     * @param string $name the name of the attribute
460
     * @return bool whether the model has an attribute with the specified name.
461
     */
462 219
    public function hasAttribute($name)
463
    {
464 219
        return isset($this->_attributes[$name]) || in_array($name, $this->attributes(), true);
465
    }
466
467
    /**
468
     * Returns the named attribute value.
469
     * If this record is the result of a query and the attribute is not loaded,
470
     * `null` will be returned.
471
     * @param string $name the attribute name
472
     * @return mixed the attribute value. `null` if the attribute is not set or does not exist.
473
     * @see hasAttribute()
474
     */
475
    public function getAttribute($name)
476
    {
477
        return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
478
    }
479
480
    /**
481
     * Sets the named attribute value.
482
     * @param string $name the attribute name
483
     * @param mixed $value the attribute value.
484
     * @throws InvalidParamException if the named attribute does not exist.
485
     * @see hasAttribute()
486
     */
487 60
    public function setAttribute($name, $value)
488
    {
489 60
        if ($this->hasAttribute($name)) {
490 60
            $this->_attributes[$name] = $value;
491 60
        } else {
492
            throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
493
        }
494 60
    }
495
496
    /**
497
     * Returns the old attribute values.
498
     * @return array the old attribute values (name-value pairs)
499
     */
500
    public function getOldAttributes()
501
    {
502
        return $this->_oldAttributes === null ? [] : $this->_oldAttributes;
503
    }
504
505
    /**
506
     * Sets the old attribute values.
507
     * All existing old attribute values will be discarded.
508
     * @param array|null $values old attribute values to be set.
509
     * If set to `null` this record is considered to be [[isNewRecord|new]].
510
     */
511 76
    public function setOldAttributes($values)
512
    {
513 76
        $this->_oldAttributes = $values;
514 76
    }
515
516
    /**
517
     * Returns the old value of the named attribute.
518
     * If this record is the result of a query and the attribute is not loaded,
519
     * `null` will be returned.
520
     * @param string $name the attribute name
521
     * @return mixed the old attribute value. `null` if the attribute is not loaded before
522
     * or does not exist.
523
     * @see hasAttribute()
524
     */
525
    public function getOldAttribute($name)
526
    {
527
        return isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
528
    }
529
530
    /**
531
     * Sets the old value of the named attribute.
532
     * @param string $name the attribute name
533
     * @param mixed $value the old attribute value.
534
     * @throws InvalidParamException if the named attribute does not exist.
535
     * @see hasAttribute()
536
     */
537
    public function setOldAttribute($name, $value)
538
    {
539
        if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) {
540
            $this->_oldAttributes[$name] = $value;
541
        } else {
542
            throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
543
        }
544
    }
545
546
    /**
547
     * Marks an attribute dirty.
548
     * This method may be called to force updating a record when calling [[update()]],
549
     * even if there is no change being made to the record.
550
     * @param string $name the attribute name
551
     */
552
    public function markAttributeDirty($name)
553
    {
554
        unset($this->_oldAttributes[$name]);
555
    }
556
557
    /**
558
     * Returns a value indicating whether the named attribute has been changed.
559
     * @param string $name the name of the attribute.
560
     * @param bool $identical whether the comparison of new and old value is made for
561
     * identical values using `===`, defaults to `true`. Otherwise `==` is used for comparison.
562
     * This parameter is available since version 2.0.4.
563
     * @return bool whether the attribute has been changed
564
     */
565 1
    public function isAttributeChanged($name, $identical = true)
566
    {
567 1
        if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) {
568 1
            if ($identical) {
569 1
                return $this->_attributes[$name] !== $this->_oldAttributes[$name];
570
            } else {
571
                return $this->_attributes[$name] != $this->_oldAttributes[$name];
572
            }
573
        } else {
574
            return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]);
575
        }
576
    }
577
578
    /**
579
     * Returns the attribute values that have been modified since they are loaded or saved most recently.
580
     *
581
     * The comparison of new and old values is made for identical values using `===`.
582
     *
583
     * @param string[]|null $names the names of the attributes whose values may be returned if they are
584
     * changed recently. If null, [[attributes()]] will be used.
585
     * @return array the changed attribute values (name-value pairs)
586
     */
587 86
    public function getDirtyAttributes($names = null)
588
    {
589 86
        if ($names === null) {
590 83
            $names = $this->attributes();
591 83
        }
592 86
        $names = array_flip($names);
593 86
        $attributes = [];
594 86
        if ($this->_oldAttributes === null) {
595 73
            foreach ($this->_attributes as $name => $value) {
596 70
                if (isset($names[$name])) {
597 70
                    $attributes[$name] = $value;
598 70
                }
599 73
            }
600 73
        } else {
601 28
            foreach ($this->_attributes as $name => $value) {
602 28
                if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) {
603 26
                    $attributes[$name] = $value;
604 26
                }
605 28
            }
606
        }
607 86
        return $attributes;
608
    }
609
610
    /**
611
     * Saves the current record.
612
     *
613
     * This method will call [[insert()]] when [[isNewRecord]] is `true`, or [[update()]]
614
     * when [[isNewRecord]] is `false`.
615
     *
616
     * For example, to save a customer record:
617
     *
618
     * ```php
619
     * $customer = new Customer; // or $customer = Customer::findOne($id);
620
     * $customer->name = $name;
621
     * $customer->email = $email;
622
     * $customer->save();
623
     * ```
624
     *
625
     * @param bool $runValidation whether to perform validation (calling [[validate()]])
626
     * before saving the record. Defaults to `true`. If the validation fails, the record
627
     * will not be saved to the database and this method will return `false`.
628
     * @param array $attributeNames list of attribute names that need to be saved. Defaults to null,
629
     * meaning all attributes that are loaded from DB will be saved.
630
     * @return bool whether the saving succeeded (i.e. no validation errors occurred).
631
     */
632 83
    public function save($runValidation = true, $attributeNames = null)
633
    {
634 83
        if ($this->getIsNewRecord()) {
635 70
            return $this->insert($runValidation, $attributeNames);
636
        } else {
637 25
            return $this->update($runValidation, $attributeNames) !== false;
638
        }
639
    }
640
641
    /**
642
     * Saves the changes to this active record into the associated database table.
643
     *
644
     * This method performs the following steps in order:
645
     *
646
     * 1. call [[beforeValidate()]] when `$runValidation` is `true`. If [[beforeValidate()]]
647
     *    returns `false`, the rest of the steps will be skipped;
648
     * 2. call [[afterValidate()]] when `$runValidation` is `true`. If validation
649
     *    failed, the rest of the steps will be skipped;
650
     * 3. call [[beforeSave()]]. If [[beforeSave()]] returns `false`,
651
     *    the rest of the steps will be skipped;
652
     * 4. save the record into database. If this fails, it will skip the rest of the steps;
653
     * 5. call [[afterSave()]];
654
     *
655
     * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
656
     * [[EVENT_AFTER_VALIDATE]], [[EVENT_BEFORE_UPDATE]], and [[EVENT_AFTER_UPDATE]]
657
     * will be raised by the corresponding methods.
658
     *
659
     * Only the [[dirtyAttributes|changed attribute values]] will be saved into database.
660
     *
661
     * For example, to update a customer record:
662
     *
663
     * ```php
664
     * $customer = Customer::findOne($id);
665
     * $customer->name = $name;
666
     * $customer->email = $email;
667
     * $customer->update();
668
     * ```
669
     *
670
     * Note that it is possible the update does not affect any row in the table.
671
     * In this case, this method will return 0. For this reason, you should use the following
672
     * code to check if update() is successful or not:
673
     *
674
     * ```php
675
     * if ($customer->update() !== false) {
676
     *     // update successful
677
     * } else {
678
     *     // update failed
679
     * }
680
     * ```
681
     *
682
     * @param bool $runValidation whether to perform validation (calling [[validate()]])
683
     * before saving the record. Defaults to `true`. If the validation fails, the record
684
     * will not be saved to the database and this method will return `false`.
685
     * @param array $attributeNames list of attribute names that need to be saved. Defaults to null,
686
     * meaning all attributes that are loaded from DB will be saved.
687
     * @return int|false the number of rows affected, or `false` if validation fails
688
     * or [[beforeSave()]] stops the updating process.
689
     * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
690
     * being updated is outdated.
691
     * @throws Exception in case update failed.
692
     */
693
    public function update($runValidation = true, $attributeNames = null)
694
    {
695
        if ($runValidation && !$this->validate($attributeNames)) {
696
            return false;
697
        }
698
        return $this->updateInternal($attributeNames);
699
    }
700
701
    /**
702
     * Updates the specified attributes.
703
     *
704
     * This method is a shortcut to [[update()]] when data validation is not needed
705
     * and only a small set attributes need to be updated.
706
     *
707
     * You may specify the attributes to be updated as name list or name-value pairs.
708
     * If the latter, the corresponding attribute values will be modified accordingly.
709
     * The method will then save the specified attributes into database.
710
     *
711
     * Note that this method will **not** perform data validation and will **not** trigger events.
712
     *
713
     * @param array $attributes the attributes (names or name-value pairs) to be updated
714
     * @return int the number of rows affected.
715
     */
716 3
    public function updateAttributes($attributes)
717
    {
718 3
        $attrs = [];
719 3
        foreach ($attributes as $name => $value) {
720 3
            if (is_int($name)) {
721
                $attrs[] = $value;
722
            } else {
723 3
                $this->$name = $value;
724 3
                $attrs[] = $name;
725
            }
726 3
        }
727
728 3
        $values = $this->getDirtyAttributes($attrs);
729 3
        if (empty($values) || $this->getIsNewRecord()) {
730 3
            return 0;
731
        }
732
733 3
        $rows = static::updateAll($values, $this->getOldPrimaryKey(true));
734
735 3
        foreach ($values as $name => $value) {
736 3
            $this->_oldAttributes[$name] = $this->_attributes[$name];
737 3
        }
738
739 3
        return $rows;
740
    }
741
742
    /**
743
     * @see update()
744
     * @param array $attributes attributes to update
745
     * @return int|false the number of rows affected, or false if [[beforeSave()]] stops the updating process.
746
     * @throws StaleObjectException
747
     */
748 25
    protected function updateInternal($attributes = null)
749
    {
750 25
        if (!$this->beforeSave(false)) {
751
            return false;
752
        }
753 25
        $values = $this->getDirtyAttributes($attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 748 can also be of type array; however, yii\db\BaseActiveRecord::getDirtyAttributes() does only seem to accept array<integer,string>|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...
754 25
        if (empty($values)) {
755 3
            $this->afterSave(false, $values);
756 3
            return 0;
757
        }
758 23
        $condition = $this->getOldPrimaryKey(true);
759 23
        $lock = $this->optimisticLock();
760 23
        if ($lock !== null) {
761 3
            $values[$lock] = $this->$lock + 1;
762 3
            $condition[$lock] = $this->$lock;
763 3
        }
764
        // We do not check the return value of updateAll() because it's possible
765
        // that the UPDATE statement doesn't change anything and thus returns 0.
766 23
        $rows = static::updateAll($values, $condition);
767
768 23
        if ($lock !== null && !$rows) {
769 3
            throw new StaleObjectException('The object being updated is outdated.');
770
        }
771
772 23
        if (isset($values[$lock])) {
773 3
            $this->$lock = $values[$lock];
774 3
        }
775
776 23
        $changedAttributes = [];
777 23
        foreach ($values as $name => $value) {
778 23
            $changedAttributes[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
779 23
            $this->_oldAttributes[$name] = $value;
780 23
        }
781 23
        $this->afterSave(false, $changedAttributes);
782
783 23
        return $rows;
784
    }
785
786
    /**
787
     * Updates one or several counter columns for the current AR object.
788
     * Note that this method differs from [[updateAllCounters()]] in that it only
789
     * saves counters for the current AR object.
790
     *
791
     * An example usage is as follows:
792
     *
793
     * ```php
794
     * $post = Post::findOne($id);
795
     * $post->updateCounters(['view_count' => 1]);
796
     * ```
797
     *
798
     * @param array $counters the counters to be updated (attribute name => increment value)
799
     * Use negative values if you want to decrement the counters.
800
     * @return bool whether the saving is successful
801
     * @see updateAllCounters()
802
     */
803 6
    public function updateCounters($counters)
804
    {
805 6
        if (static::updateAllCounters($counters, $this->getOldPrimaryKey(true)) > 0) {
806 6
            foreach ($counters as $name => $value) {
807 6
                if (!isset($this->_attributes[$name])) {
808 3
                    $this->_attributes[$name] = $value;
809 3
                } else {
810 3
                    $this->_attributes[$name] += $value;
811
                }
812 6
                $this->_oldAttributes[$name] = $this->_attributes[$name];
813 6
            }
814 6
            return true;
815
        } else {
816
            return false;
817
        }
818
    }
819
820
    /**
821
     * Deletes the table row corresponding to this active record.
822
     *
823
     * This method performs the following steps in order:
824
     *
825
     * 1. call [[beforeDelete()]]. If the method returns `false`, it will skip the
826
     *    rest of the steps;
827
     * 2. delete the record from the database;
828
     * 3. call [[afterDelete()]].
829
     *
830
     * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
831
     * will be raised by the corresponding methods.
832
     *
833
     * @return int|false the number of rows deleted, or `false` if the deletion is unsuccessful for some reason.
834
     * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
835
     * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
836
     * being deleted is outdated.
837
     * @throws Exception in case delete failed.
838
     */
839
    public function delete()
840
    {
841
        $result = false;
842
        if ($this->beforeDelete()) {
843
            // we do not check the return value of deleteAll() because it's possible
844
            // the record is already deleted in the database and thus the method will return 0
845
            $condition = $this->getOldPrimaryKey(true);
846
            $lock = $this->optimisticLock();
847
            if ($lock !== null) {
848
                $condition[$lock] = $this->$lock;
849
            }
850
            $result = static::deleteAll($condition);
851
            if ($lock !== null && !$result) {
852
                throw new StaleObjectException('The object being deleted is outdated.');
853
            }
854
            $this->_oldAttributes = null;
855
            $this->afterDelete();
856
        }
857
858
        return $result;
859
    }
860
861
    /**
862
     * Returns a value indicating whether the current record is new.
863
     * @return bool whether the record is new and should be inserted when calling [[save()]].
864
     */
865 116
    public function getIsNewRecord()
866
    {
867 116
        return $this->_oldAttributes === null;
868
    }
869
870
    /**
871
     * Sets the value indicating whether the record is new.
872
     * @param bool $value whether the record is new and should be inserted when calling [[save()]].
873
     * @see getIsNewRecord()
874
     */
875
    public function setIsNewRecord($value)
876
    {
877
        $this->_oldAttributes = $value ? null : $this->_attributes;
878
    }
879
880
    /**
881
     * Initializes the object.
882
     * This method is called at the end of the constructor.
883
     * The default implementation will trigger an [[EVENT_INIT]] event.
884
     * If you override this method, make sure you call the parent implementation at the end
885
     * to ensure triggering of the event.
886
     */
887 305
    public function init()
888
    {
889 305
        parent::init();
890 305
        $this->trigger(self::EVENT_INIT);
891 305
    }
892
893
    /**
894
     * This method is called when the AR object is created and populated with the query result.
895
     * The default implementation will trigger an [[EVENT_AFTER_FIND]] event.
896
     * When overriding this method, make sure you call the parent implementation to ensure the
897
     * event is triggered.
898
     */
899 253
    public function afterFind()
900
    {
901 253
        $this->trigger(self::EVENT_AFTER_FIND);
902 253
    }
903
904
    /**
905
     * This method is called at the beginning of inserting or updating a record.
906
     * The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is `true`,
907
     * or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is `false`.
908
     * When overriding this method, make sure you call the parent implementation like the following:
909
     *
910
     * ```php
911
     * public function beforeSave($insert)
912
     * {
913
     *     if (parent::beforeSave($insert)) {
914
     *         // ...custom code here...
915
     *         return true;
916
     *     } else {
917
     *         return false;
918
     *     }
919
     * }
920
     * ```
921
     *
922
     * @param bool $insert whether this method called while inserting a record.
923
     * If `false`, it means the method is called while updating a record.
924
     * @return bool whether the insertion or updating should continue.
925
     * If `false`, the insertion or updating will be cancelled.
926
     */
927 83
    public function beforeSave($insert)
928
    {
929 83
        $event = new ModelEvent;
930 83
        $this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event);
931
932 83
        return $event->isValid;
933
    }
934
935
    /**
936
     * This method is called at the end of inserting or updating a record.
937
     * The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is `true`,
938
     * or an [[EVENT_AFTER_UPDATE]] event if `$insert` is `false`. The event class used is [[AfterSaveEvent]].
939
     * When overriding this method, make sure you call the parent implementation so that
940
     * the event is triggered.
941
     * @param bool $insert whether this method called while inserting a record.
942
     * If `false`, it means the method is called while updating a record.
943
     * @param array $changedAttributes The old values of attributes that had changed and were saved.
944
     * You can use this parameter to take action based on the changes made for example send an email
945
     * when the password had changed or implement audit trail that tracks all the changes.
946
     * `$changedAttributes` gives you the old attribute values while the active record (`$this`) has
947
     * already the new, updated values.
948
     *
949
     * Note that no automatic type conversion performed by default. You may use
950
     * [[\yii\behaviors\AttributeTypecastBehavior]] to facilitate attribute typecasting.
951
     * See http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#attributes-typecasting.
952
     */
953 83
    public function afterSave($insert, $changedAttributes)
954
    {
955 83
        $this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE, new AfterSaveEvent([
956 83
            'changedAttributes' => $changedAttributes,
957 83
        ]));
958 83
    }
959
960
    /**
961
     * This method is invoked before deleting a record.
962
     * The default implementation raises the [[EVENT_BEFORE_DELETE]] event.
963
     * When overriding this method, make sure you call the parent implementation like the following:
964
     *
965
     * ```php
966
     * public function beforeDelete()
967
     * {
968
     *     if (parent::beforeDelete()) {
969
     *         // ...custom code here...
970
     *         return true;
971
     *     } else {
972
     *         return false;
973
     *     }
974
     * }
975
     * ```
976
     *
977
     * @return bool whether the record should be deleted. Defaults to `true`.
978
     */
979 6
    public function beforeDelete()
980
    {
981 6
        $event = new ModelEvent;
982 6
        $this->trigger(self::EVENT_BEFORE_DELETE, $event);
983
984 6
        return $event->isValid;
985
    }
986
987
    /**
988
     * This method is invoked after deleting a record.
989
     * The default implementation raises the [[EVENT_AFTER_DELETE]] event.
990
     * You may override this method to do postprocessing after the record is deleted.
991
     * Make sure you call the parent implementation so that the event is raised properly.
992
     */
993 6
    public function afterDelete()
994
    {
995 6
        $this->trigger(self::EVENT_AFTER_DELETE);
996 6
    }
997
998
    /**
999
     * Repopulates this active record with the latest data.
1000
     *
1001
     * If the refresh is successful, an [[EVENT_AFTER_REFRESH]] event will be triggered.
1002
     * This event is available since version 2.0.8.
1003
     *
1004
     * @return bool whether the row still exists in the database. If `true`, the latest data
1005
     * will be populated to this active record. Otherwise, this record will remain unchanged.
1006
     */
1007 19
    public function refresh()
1008
    {
1009
        /* @var $record BaseActiveRecord */
1010 19
        $record = static::findOne($this->getPrimaryKey(true));
1011 19
        if ($record === null) {
1012 3
            return false;
1013
        }
1014 19
        foreach ($this->attributes() as $name) {
1015 19
            $this->_attributes[$name] = isset($record->_attributes[$name]) ? $record->_attributes[$name] : null;
1016 19
        }
1017 19
        $this->_oldAttributes = $record->_oldAttributes;
1018 19
        $this->_related = [];
1019 19
        $this->afterRefresh();
1020
1021 19
        return true;
1022
    }
1023
1024
    /**
1025
     * This method is called when the AR object is refreshed.
1026
     * The default implementation will trigger an [[EVENT_AFTER_REFRESH]] event.
1027
     * When overriding this method, make sure you call the parent implementation to ensure the
1028
     * event is triggered.
1029
     * @since 2.0.8
1030
     */
1031 19
    public function afterRefresh()
1032
    {
1033 19
        $this->trigger(self::EVENT_AFTER_REFRESH);
1034 19
    }
1035
1036
    /**
1037
     * Returns a value indicating whether the given active record is the same as the current one.
1038
     * The comparison is made by comparing the table names and the primary key values of the two active records.
1039
     * If one of the records [[isNewRecord|is new]] they are also considered not equal.
1040
     * @param ActiveRecordInterface $record record to compare to
1041
     * @return bool whether the two active records refer to the same row in the same database table.
1042
     */
1043
    public function equals($record)
1044
    {
1045
        if ($this->getIsNewRecord() || $record->getIsNewRecord()) {
1046
            return false;
1047
        }
1048
1049
        return get_class($this) === get_class($record) && $this->getPrimaryKey() === $record->getPrimaryKey();
1050
    }
1051
1052
    /**
1053
     * Returns the primary key value(s).
1054
     * @param bool $asArray whether to return the primary key value as an array. If `true`,
1055
     * the return value will be an array with column names as keys and column values as values.
1056
     * Note that for composite primary keys, an array will always be returned regardless of this parameter value.
1057
     * @property mixed The primary key value. An array (column name => column value) is returned if
1058
     * the primary key is composite. A string is returned otherwise (null will be returned if
1059
     * the key value is null).
1060
     * @return mixed the primary key value. An array (column name => column value) is returned if the primary key
1061
     * is composite or `$asArray` is `true`. A string is returned otherwise (null will be returned if
1062
     * the key value is null).
1063
     */
1064 48
    public function getPrimaryKey($asArray = false)
1065
    {
1066 48
        $keys = $this->primaryKey();
1067 48
        if (!$asArray && count($keys) === 1) {
1068 26
            return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null;
1069
        } else {
1070 25
            $values = [];
1071 25
            foreach ($keys as $name) {
1072 25
                $values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
1073 25
            }
1074
1075 25
            return $values;
1076
        }
1077
    }
1078
1079
    /**
1080
     * Returns the old primary key value(s).
1081
     * This refers to the primary key value that is populated into the record
1082
     * after executing a find method (e.g. find(), findOne()).
1083
     * The value remains unchanged even if the primary key attribute is manually assigned with a different value.
1084
     * @param bool $asArray whether to return the primary key value as an array. If `true`,
1085
     * the return value will be an array with column name as key and column value as value.
1086
     * If this is `false` (default), a scalar value will be returned for non-composite primary key.
1087
     * @property mixed The old primary key value. An array (column name => column value) is
1088
     * returned if the primary key is composite. A string is returned otherwise (null will be
1089
     * returned if the key value is null).
1090
     * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key
1091
     * is composite or `$asArray` is `true`. A string is returned otherwise (null will be returned if
1092
     * the key value is null).
1093
     * @throws Exception if the AR model does not have a primary key
1094
     */
1095 47
    public function getOldPrimaryKey($asArray = false)
1096
    {
1097 47
        $keys = $this->primaryKey();
1098 47
        if (empty($keys)) {
1099
            throw new Exception(get_class($this) . ' does not have a primary key. You should either define a primary key for the corresponding table or override the primaryKey() method.');
1100
        }
1101 47
        if (!$asArray && count($keys) === 1) {
1102 7
            return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null;
1103
        } else {
1104 41
            $values = [];
1105 41
            foreach ($keys as $name) {
1106 41
                $values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
1107 41
            }
1108
1109 41
            return $values;
1110
        }
1111
    }
1112
1113
    /**
1114
     * Populates an active record object using a row of data from the database/storage.
1115
     *
1116
     * This is an internal method meant to be called to create active record objects after
1117
     * fetching data from the database. It is mainly used by [[ActiveQuery]] to populate
1118
     * the query results into active records.
1119
     *
1120
     * When calling this method manually you should call [[afterFind()]] on the created
1121
     * record to trigger the [[EVENT_AFTER_FIND|afterFind Event]].
1122
     *
1123
     * @param BaseActiveRecord $record the record to be populated. In most cases this will be an instance
1124
     * created by [[instantiate()]] beforehand.
1125
     * @param array $row attribute values (name => value)
1126
     */
1127 253
    public static function populateRecord($record, $row)
1128
    {
1129 253
        $columns = array_flip($record->attributes());
1130 253
        foreach ($row as $name => $value) {
1131 253
            if (isset($columns[$name])) {
1132 253
                $record->_attributes[$name] = $value;
1133 253
            } elseif ($record->canSetProperty($name)) {
1134 6
                $record->$name = $value;
1135 6
            }
1136 253
        }
1137 253
        $record->_oldAttributes = $record->_attributes;
1138 253
    }
1139
1140
    /**
1141
     * Creates an active record instance.
1142
     *
1143
     * This method is called together with [[populateRecord()]] by [[ActiveQuery]].
1144
     * It is not meant to be used for creating new records directly.
1145
     *
1146
     * You may override this method if the instance being created
1147
     * depends on the row data to be populated into the record.
1148
     * For example, by creating a record based on the value of a column,
1149
     * you may implement the so-called single-table inheritance mapping.
1150
     * @param array $row row data to be populated into the record.
1151
     * @return static the newly created active record
1152
     */
1153 250
    public static function instantiate($row)
0 ignored issues
show
Unused Code introduced by
The parameter $row is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1154
    {
1155 250
        return new static;
1156
    }
1157
1158
    /**
1159
     * Returns whether there is an element at the specified offset.
1160
     * This method is required by the interface [[\ArrayAccess]].
1161
     * @param mixed $offset the offset to check on
1162
     * @return bool whether there is an element at the specified offset.
1163
     */
1164 24
    public function offsetExists($offset)
1165
    {
1166 24
        return $this->__isset($offset);
1167
    }
1168
1169
    /**
1170
     * Returns the relation object with the specified name.
1171
     * A relation is defined by a getter method which returns an [[ActiveQueryInterface]] object.
1172
     * It can be declared in either the Active Record class itself or one of its behaviors.
1173
     * @param string $name the relation name, e.g. `orders` for a relation defined via `getOrders()` method (case-sensitive).
1174
     * @param bool $throwException whether to throw exception if the relation does not exist.
1175
     * @return ActiveQueryInterface|ActiveQuery the relational query object. If the relation does not exist
1176
     * and `$throwException` is `false`, `null` will be returned.
1177
     * @throws InvalidParamException if the named relation does not exist.
1178
     */
1179 120
    public function getRelation($name, $throwException = true)
1180
    {
1181 120
        $getter = 'get' . $name;
1182
        try {
1183
            // the relation could be defined in a behavior
1184 120
            $relation = $this->$getter();
1185 120
        } catch (UnknownMethodException $e) {
1186
            if ($throwException) {
1187
                throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
1188
            } else {
1189
                return null;
1190
            }
1191
        }
1192 120
        if (!$relation instanceof ActiveQueryInterface) {
1193
            if ($throwException) {
1194
                throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".');
1195
            } else {
1196
                return null;
1197
            }
1198
        }
1199
1200 120
        if (method_exists($this, $getter)) {
1201
            // relation name is case sensitive, trying to validate it when the relation is defined within this class
1202 120
            $method = new \ReflectionMethod($this, $getter);
1203 120
            $realName = lcfirst(substr($method->getName(), 3));
1204 120
            if ($realName !== $name) {
1205
                if ($throwException) {
1206
                    throw new InvalidParamException('Relation names are case sensitive. ' . get_class($this) . " has a relation named \"$realName\" instead of \"$name\".");
1207
                } else {
1208
                    return null;
1209
                }
1210
            }
1211 120
        }
1212
1213 120
        return $relation;
1214
    }
1215
1216
    /**
1217
     * Establishes the relationship between two models.
1218
     *
1219
     * The relationship is established by setting the foreign key value(s) in one model
1220
     * to be the corresponding primary key value(s) in the other model.
1221
     * The model with the foreign key will be saved into database without performing validation.
1222
     *
1223
     * If the relationship involves a junction table, a new row will be inserted into the
1224
     * junction table which contains the primary key values from both models.
1225
     *
1226
     * Note that this method requires that the primary key value is not null.
1227
     *
1228
     * @param string $name the case sensitive name of the relationship, e.g. `orders` for a relation defined via `getOrders()` method.
1229
     * @param ActiveRecordInterface $model the model to be linked with the current one.
1230
     * @param array $extraColumns additional column values to be saved into the junction table.
1231
     * This parameter is only meaningful for a relationship involving a junction table
1232
     * (i.e., a relation set with [[ActiveRelationTrait::via()]] or [[ActiveQuery::viaTable()]].)
1233
     * @throws InvalidCallException if the method is unable to link two models.
1234
     */
1235 9
    public function link($name, $model, $extraColumns = [])
1236
    {
1237 9
        $relation = $this->getRelation($name);
1238
1239 9
        if ($relation->via !== null) {
0 ignored issues
show
Bug introduced by
Accessing via 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...
1240 3
            if ($this->getIsNewRecord() || $model->getIsNewRecord()) {
1241
                throw new InvalidCallException('Unable to link models: the models being linked cannot be newly created.');
1242
            }
1243 3
            if (is_array($relation->via)) {
0 ignored issues
show
Bug introduced by
Accessing via 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...
1244
                /* @var $viaRelation ActiveQuery */
1245 3
                list($viaName, $viaRelation) = $relation->via;
0 ignored issues
show
Bug introduced by
Accessing via 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...
1246 3
                $viaClass = $viaRelation->modelClass;
1247
                // unset $viaName so that it can be reloaded to reflect the change
1248 3
                unset($this->_related[$viaName]);
1249 3
            } else {
1250
                $viaRelation = $relation->via;
0 ignored issues
show
Bug introduced by
Accessing via 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...
1251
                $viaTable = reset($relation->via->from);
0 ignored issues
show
Bug introduced by
Accessing via 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...
1252
            }
1253 3
            $columns = [];
1254 3
            foreach ($viaRelation->link as $a => $b) {
1255 3
                $columns[$a] = $this->$b;
1256 3
            }
1257 3
            foreach ($relation->link as $a => $b) {
0 ignored issues
show
Bug introduced by
Accessing link 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...
1258 3
                $columns[$b] = $model->$a;
1259 3
            }
1260 3
            foreach ($extraColumns as $k => $v) {
1261 3
                $columns[$k] = $v;
1262 3
            }
1263 3
            if (is_array($relation->via)) {
0 ignored issues
show
Bug introduced by
Accessing via 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...
1264
                /* @var $viaClass ActiveRecordInterface */
1265
                /* @var $record ActiveRecordInterface */
1266 3
                $record = new $viaClass();
0 ignored issues
show
Bug introduced by
The variable $viaClass does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1267 3
                foreach ($columns as $column => $value) {
1268 3
                    $record->$column = $value;
1269 3
                }
1270 3
                $record->insert(false);
1271 3
            } else {
1272
                /* @var $viaTable string */
1273
                static::getDb()->createCommand()
1274
                    ->insert($viaTable, $columns)->execute();
0 ignored issues
show
Bug introduced by
The variable $viaTable does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1275
            }
1276 3
        } else {
1277 9
            $p1 = $model->isPrimaryKey(array_keys($relation->link));
0 ignored issues
show
Bug introduced by
Accessing link 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...
1278 9
            $p2 = static::isPrimaryKey(array_values($relation->link));
0 ignored issues
show
Bug introduced by
Accessing link 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...
1279 9
            if ($p1 && $p2) {
1280
                if ($this->getIsNewRecord() && $model->getIsNewRecord()) {
1281
                    throw new InvalidCallException('Unable to link models: at most one model can be newly created.');
1282
                } elseif ($this->getIsNewRecord()) {
1283
                    $this->bindModels(array_flip($relation->link), $this, $model);
0 ignored issues
show
Bug introduced by
Accessing link 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...
1284
                } else {
1285
                    $this->bindModels($relation->link, $model, $this);
0 ignored issues
show
Bug introduced by
Accessing link 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...
1286
                }
1287 9
            } elseif ($p1) {
1288 3
                $this->bindModels(array_flip($relation->link), $this, $model);
0 ignored issues
show
Bug introduced by
Accessing link 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...
1289 9
            } elseif ($p2) {
1290 9
                $this->bindModels($relation->link, $model, $this);
0 ignored issues
show
Bug introduced by
Accessing link 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...
1291 9
            } else {
1292
                throw new InvalidCallException('Unable to link models: the link defining the relation does not involve any primary key.');
1293
            }
1294
        }
1295
1296
        // update lazily loaded related objects
1297 9
        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...
1298 3
            $this->_related[$name] = $model;
1299 9
        } elseif (isset($this->_related[$name])) {
1300 9
            if ($relation->indexBy !== null) {
0 ignored issues
show
Bug introduced by
Accessing indexBy 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...
1301 6
                if ($relation->indexBy instanceof \Closure) {
0 ignored issues
show
Bug introduced by
Accessing indexBy 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...
1302 3
                    $index = call_user_func($relation->indexBy, $model);
0 ignored issues
show
Bug introduced by
Accessing indexBy 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...
1303 3
                } else {
1304 3
                    $index = $model->{$relation->indexBy};
0 ignored issues
show
Bug introduced by
Accessing indexBy 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...
1305
                }
1306 6
                $this->_related[$name][$index] = $model;
1307 6
            } else {
1308 3
                $this->_related[$name][] = $model;
1309
            }
1310 9
        }
1311 9
    }
1312
1313
    /**
1314
     * Destroys the relationship between two models.
1315
     *
1316
     * The model with the foreign key of the relationship will be deleted if `$delete` is `true`.
1317
     * Otherwise, the foreign key will be set `null` and the model will be saved without validation.
1318
     *
1319
     * @param string $name the case sensitive name of the relationship, e.g. `orders` for a relation defined via `getOrders()` method.
1320
     * @param ActiveRecordInterface $model the model to be unlinked from the current one.
1321
     * You have to make sure that the model is really related with the current model as this method
1322
     * does not check this.
1323
     * @param bool $delete whether to delete the model that contains the foreign key.
1324
     * If `false`, the model's foreign key will be set `null` and saved.
1325
     * If `true`, the model containing the foreign key will be deleted.
1326
     * @throws InvalidCallException if the models cannot be unlinked
1327
     */
1328 3
    public function unlink($name, $model, $delete = false)
1329
    {
1330 3
        $relation = $this->getRelation($name);
1331
1332 3
        if ($relation->via !== null) {
0 ignored issues
show
Bug introduced by
Accessing via 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...
1333 3
            if (is_array($relation->via)) {
0 ignored issues
show
Bug introduced by
Accessing via 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...
1334
                /* @var $viaRelation ActiveQuery */
1335 3
                list($viaName, $viaRelation) = $relation->via;
0 ignored issues
show
Bug introduced by
Accessing via 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...
1336 3
                $viaClass = $viaRelation->modelClass;
1337 3
                unset($this->_related[$viaName]);
1338 3
            } else {
1339 3
                $viaRelation = $relation->via;
0 ignored issues
show
Bug introduced by
Accessing via 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...
1340 3
                $viaTable = reset($relation->via->from);
0 ignored issues
show
Bug introduced by
Accessing via 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...
1341
            }
1342 3
            $columns = [];
1343 3
            foreach ($viaRelation->link as $a => $b) {
1344 3
                $columns[$a] = $this->$b;
1345 3
            }
1346 3
            foreach ($relation->link as $a => $b) {
0 ignored issues
show
Bug introduced by
Accessing link 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...
1347 3
                $columns[$b] = $model->$a;
1348 3
            }
1349 3
            $nulls = [];
1350 3
            foreach (array_keys($columns) as $a) {
1351 3
                $nulls[$a] = null;
1352 3
            }
1353 3
            if (is_array($relation->via)) {
0 ignored issues
show
Bug introduced by
Accessing via 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...
1354
                /* @var $viaClass ActiveRecordInterface */
1355 3
                if ($delete) {
1356 3
                    $viaClass::deleteAll($columns);
0 ignored issues
show
Bug introduced by
The variable $viaClass does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1357 3
                } else {
1358
                    $viaClass::updateAll($nulls, $columns);
1359
                }
1360 3
            } else {
1361
                /* @var $viaTable string */
1362
                /* @var $command Command */
1363 3
                $command = static::getDb()->createCommand();
1364 3
                if ($delete) {
1365
                    $command->delete($viaTable, $columns)->execute();
0 ignored issues
show
Bug introduced by
The variable $viaTable does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1366
                } else {
1367 3
                    $command->update($viaTable, $nulls, $columns)->execute();
1368
                }
1369
            }
1370 3
        } else {
1371 3
            $p1 = $model->isPrimaryKey(array_keys($relation->link));
0 ignored issues
show
Bug introduced by
Accessing link 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...
1372 3
            $p2 = static::isPrimaryKey(array_values($relation->link));
0 ignored issues
show
Bug introduced by
Accessing link 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...
1373 3
            if ($p2) {
1374 3
                if ($delete) {
1375 3
                    $model->delete();
1376 3
                } else {
1377 3
                    foreach ($relation->link as $a => $b) {
0 ignored issues
show
Bug introduced by
Accessing link 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...
1378 3
                        $model->$a = null;
1379 3
                    }
1380 3
                    $model->save(false);
1381
                }
1382 3
            } elseif ($p1) {
1383
                foreach ($relation->link as $a => $b) {
0 ignored issues
show
Bug introduced by
Accessing link 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...
1384
                    if (is_array($this->$b)) { // relation via array valued attribute
1385
                        if (($key = array_search($model->$a, $this->$b, false)) !== false) {
1386
                            $values = $this->$b;
1387
                            unset($values[$key]);
1388
                            $this->$b = array_values($values);
1389
                        }
1390
                    } else {
1391
                        $this->$b = null;
1392
                    }
1393
                }
1394
                $delete ? $this->delete() : $this->save(false);
1395
            } else {
1396
                throw new InvalidCallException('Unable to unlink models: the link does not involve any primary key.');
1397
            }
1398
        }
1399
1400 3
        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...
1401
            unset($this->_related[$name]);
1402 3
        } elseif (isset($this->_related[$name])) {
1403
            /* @var $b ActiveRecordInterface */
1404 3
            foreach ($this->_related[$name] as $a => $b) {
1405 3
                if ($model->getPrimaryKey() === $b->getPrimaryKey()) {
1406 3
                    unset($this->_related[$name][$a]);
1407 3
                }
1408 3
            }
1409 3
        }
1410 3
    }
1411
1412
    /**
1413
     * Destroys the relationship in current model.
1414
     *
1415
     * The model with the foreign key of the relationship will be deleted if `$delete` is `true`.
1416
     * Otherwise, the foreign key will be set `null` and the model will be saved without validation.
1417
     *
1418
     * Note that to destroy the relationship without removing records make sure your keys can be set to null
1419
     *
1420
     * @param string $name the case sensitive name of the relationship, e.g. `orders` for a relation defined via `getOrders()` method.
1421
     * @param bool $delete whether to delete the model that contains the foreign key.
1422
     */
1423 18
    public function unlinkAll($name, $delete = false)
1424
    {
1425 18
        $relation = $this->getRelation($name);
1426
1427 18
        if ($relation->via !== null) {
0 ignored issues
show
Bug introduced by
Accessing via 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...
1428 9
            if (is_array($relation->via)) {
0 ignored issues
show
Bug introduced by
Accessing via 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...
1429
                /* @var $viaRelation ActiveQuery */
1430 6
                list($viaName, $viaRelation) = $relation->via;
0 ignored issues
show
Bug introduced by
Accessing via 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...
1431 6
                $viaClass = $viaRelation->modelClass;
1432 6
                unset($this->_related[$viaName]);
1433 6
            } else {
1434 3
                $viaRelation = $relation->via;
0 ignored issues
show
Bug introduced by
Accessing via 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...
1435 3
                $viaTable = reset($relation->via->from);
0 ignored issues
show
Bug introduced by
Accessing via 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...
1436
            }
1437 9
            $condition = [];
1438 9
            $nulls = [];
1439 9
            foreach ($viaRelation->link as $a => $b) {
1440 9
                $nulls[$a] = null;
1441 9
                $condition[$a] = $this->$b;
1442 9
            }
1443 9
            if (!empty($viaRelation->where)) {
1444
                $condition = ['and', $condition, $viaRelation->where];
1445
            }
1446 9
            if (!empty($viaRelation->on)) {
1447
                $condition = ['and', $condition, $viaRelation->on];
1448
            }
1449 9
            if (is_array($relation->via)) {
0 ignored issues
show
Bug introduced by
Accessing via 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...
1450
                /* @var $viaClass ActiveRecordInterface */
1451 6
                if ($delete) {
1452 6
                    $viaClass::deleteAll($condition);
0 ignored issues
show
Bug introduced by
The variable $viaClass does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1453 6
                } else {
1454 3
                    $viaClass::updateAll($nulls, $condition);
1455
                }
1456 6
            } else {
1457
                /* @var $viaTable string */
1458
                /* @var $command Command */
1459 3
                $command = static::getDb()->createCommand();
1460 3
                if ($delete) {
1461 3
                    $command->delete($viaTable, $condition)->execute();
0 ignored issues
show
Bug introduced by
The variable $viaTable does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1462 3
                } else {
1463 3
                    $command->update($viaTable, $nulls, $condition)->execute();
1464
                }
1465
            }
1466 9
        } else {
1467
            /* @var $relatedModel ActiveRecordInterface */
1468 12
            $relatedModel = $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...
1469 12
            if (!$delete && count($relation->link) === 1 && is_array($this->{$b = reset($relation->link)})) {
0 ignored issues
show
Bug introduced by
Accessing link 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...
1470
                // relation via array valued attribute
1471
                $this->$b = [];
1472
                $this->save(false);
1473
            } else {
1474 12
                $nulls = [];
1475 12
                $condition = [];
1476 12
                foreach ($relation->link as $a => $b) {
0 ignored issues
show
Bug introduced by
Accessing link 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...
1477 12
                    $nulls[$a] = null;
1478 12
                    $condition[$a] = $this->$b;
1479 12
                }
1480 12
                if (!empty($relation->where)) {
0 ignored issues
show
Bug introduced by
Accessing where 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...
1481 6
                    $condition = ['and', $condition, $relation->where];
0 ignored issues
show
Bug introduced by
Accessing where 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...
1482 6
                }
1483 12
                if (!empty($relation->on)) {
0 ignored issues
show
Bug introduced by
Accessing on 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...
1484 3
                    $condition = ['and', $condition, $relation->on];
0 ignored issues
show
Bug introduced by
Accessing on 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...
1485 3
                }
1486 12
                if ($delete) {
1487 9
                    $relatedModel::deleteAll($condition);
1488 9
                } else {
1489 6
                    $relatedModel::updateAll($nulls, $condition);
1490
                }
1491
            }
1492
        }
1493
1494 18
        unset($this->_related[$name]);
1495 18
    }
1496
1497
    /**
1498
     * @param array $link
1499
     * @param ActiveRecordInterface $foreignModel
1500
     * @param ActiveRecordInterface $primaryModel
1501
     * @throws InvalidCallException
1502
     */
1503 9
    private function bindModels($link, $foreignModel, $primaryModel)
1504
    {
1505 9
        foreach ($link as $fk => $pk) {
1506 9
            $value = $primaryModel->$pk;
1507 9
            if ($value === null) {
1508
                throw new InvalidCallException('Unable to link models: the primary key of ' . get_class($primaryModel) . ' is null.');
1509
            }
1510 9
            if (is_array($foreignModel->$fk)) { // relation via array valued attribute
1511
                $foreignModel->$fk = array_merge($foreignModel->$fk, [$value]);
1512
            } else {
1513 9
                $foreignModel->$fk = $value;
1514
            }
1515 9
        }
1516 9
        $foreignModel->save(false);
1517 9
    }
1518
1519
    /**
1520
     * Returns a value indicating whether the given set of attributes represents the primary key for this model
1521
     * @param array $keys the set of attributes to check
1522
     * @return bool whether the given set of attributes represents the primary key for this model
1523
     */
1524 15
    public static function isPrimaryKey($keys)
1525
    {
1526 15
        $pks = static::primaryKey();
1527 15
        if (count($keys) === count($pks)) {
1528 15
            return count(array_intersect($keys, $pks)) === count($pks);
1529
        } else {
1530 9
            return false;
1531
        }
1532
    }
1533
1534
    /**
1535
     * Returns the text label for the specified attribute.
1536
     * If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model.
1537
     * @param string $attribute the attribute name
1538
     * @return string the attribute label
1539
     * @see generateAttributeLabel()
1540
     * @see attributeLabels()
1541
     */
1542 51
    public function getAttributeLabel($attribute)
1543
    {
1544 51
        $labels = $this->attributeLabels();
1545 51
        if (isset($labels[$attribute])) {
1546 10
            return $labels[$attribute];
1547 48
        } elseif (strpos($attribute, '.')) {
1548
            $attributeParts = explode('.', $attribute);
1549
            $neededAttribute = array_pop($attributeParts);
1550
1551
            $relatedModel = $this;
1552
            foreach ($attributeParts as $relationName) {
1553
                if ($relatedModel->isRelationPopulated($relationName) && $relatedModel->$relationName instanceof self) {
1554
                    $relatedModel = $relatedModel->$relationName;
1555
                } else {
1556
                    try {
1557
                        $relation = $relatedModel->getRelation($relationName);
1558
                    } catch (InvalidParamException $e) {
1559
                        return $this->generateAttributeLabel($attribute);
1560
                    }
1561
                    $relatedModel = new $relation->modelClass;
1562
                }
1563
            }
1564
1565
            $labels = $relatedModel->attributeLabels();
1566
            if (isset($labels[$neededAttribute])) {
1567
                return $labels[$neededAttribute];
1568
            }
1569
        }
1570
1571 48
        return $this->generateAttributeLabel($attribute);
1572
    }
1573
1574
    /**
1575
     * Returns the text hint for the specified attribute.
1576
     * If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model.
1577
     * @param string $attribute the attribute name
1578
     * @return string the attribute hint
1579
     * @see attributeHints()
1580
     * @since 2.0.4
1581
     */
1582
    public function getAttributeHint($attribute)
1583
    {
1584
        $hints = $this->attributeHints();
1585
        if (isset($hints[$attribute])) {
1586
            return $hints[$attribute];
1587
        } elseif (strpos($attribute, '.')) {
1588
            $attributeParts = explode('.', $attribute);
1589
            $neededAttribute = array_pop($attributeParts);
1590
1591
            $relatedModel = $this;
1592
            foreach ($attributeParts as $relationName) {
1593
                if ($relatedModel->isRelationPopulated($relationName) && $relatedModel->$relationName instanceof self) {
1594
                    $relatedModel = $relatedModel->$relationName;
1595
                } else {
1596
                    try {
1597
                        $relation = $relatedModel->getRelation($relationName);
1598
                    } catch (InvalidParamException $e) {
1599
                        return '';
1600
                    }
1601
                    $relatedModel = new $relation->modelClass;
1602
                }
1603
            }
1604
1605
            $hints = $relatedModel->attributeHints();
1606
            if (isset($hints[$neededAttribute])) {
1607
                return $hints[$neededAttribute];
1608
            }
1609
        }
1610
        return '';
1611
    }
1612
1613
    /**
1614
     * @inheritdoc
1615
     *
1616
     * The default implementation returns the names of the columns whose values have been populated into this record.
1617
     */
1618
    public function fields()
1619
    {
1620
        $fields = array_keys($this->_attributes);
1621
1622
        return array_combine($fields, $fields);
1623
    }
1624
1625
    /**
1626
     * @inheritdoc
1627
     *
1628
     * The default implementation returns the names of the relations that have been populated into this record.
1629
     */
1630
    public function extraFields()
1631
    {
1632
        $fields = array_keys($this->getRelatedRecords());
1633
1634
        return array_combine($fields, $fields);
1635
    }
1636
1637
    /**
1638
     * Sets the element value at the specified offset to null.
1639
     * This method is required by the SPL interface [[\ArrayAccess]].
1640
     * It is implicitly called when you use something like `unset($model[$offset])`.
1641
     * @param mixed $offset the offset to unset element
1642
     */
1643 3
    public function offsetUnset($offset)
1644
    {
1645 3
        if (property_exists($this, $offset)) {
1646
            $this->$offset = null;
1647
        } else {
1648 3
            unset($this->$offset);
1649
        }
1650 3
    }
1651
}
1652