Completed
Push — ar-unlink-do-not-set-fk-before... ( a08970 )
by Alexander
08:28
created

BaseActiveRecord::updateCounters()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.0119
Metric Value
dl 0
loc 16
rs 9.2
ccs 10
cts 11
cp 0.9091
cc 4
eloc 11
nc 4
nop 1
crap 4.0119
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 boolean $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
    /**
82
     * @var array attribute values indexed by attribute names
83
     */
84
    private $_attributes = [];
85
    /**
86
     * @var array|null old attribute values indexed by attribute names.
87
     * This is `null` if the record [[isNewRecord|is new]].
88
     */
89
    private $_oldAttributes;
90
    /**
91
     * @var array related models indexed by the relation names
92
     */
93
    private $_related = [];
94
95
96
    /**
97
     * @inheritdoc
98
     * @return static|null ActiveRecord instance matching the condition, or `null` if nothing matches.
99
     */
100 127
    public static function findOne($condition)
101
    {
102 127
        return static::findByCondition($condition)->one();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return static::findByCon...ion($condition)->one(); (array|boolean) is incompatible with the return type declared by the interface yii\db\ActiveRecordInterface::findOne of type yii\db\ActiveRecordInterface|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
103
    }
104
105
    /**
106
     * @inheritdoc
107
     * @return static[] an array of ActiveRecord instances, or an empty array if nothing matches.
108
     */
109
    public static function findAll($condition)
110
    {
111
        return static::findByCondition($condition)->all();
112
    }
113
114
    /**
115
     * Finds ActiveRecord instance(s) by the given condition.
116
     * This method is internally called by [[findOne()]] and [[findAll()]].
117
     * @param mixed $condition please refer to [[findOne()]] for the explanation of this parameter
118
     * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance.
119
     * @throws InvalidConfigException if there is no primary key defined
120
     * @internal
121
     */
122
    protected static function findByCondition($condition)
123
    {
124
        $query = static::find();
125
126
        if (!ArrayHelper::isAssociative($condition)) {
127
            // query by primary key
128
            $primaryKey = static::primaryKey();
129
            if (isset($primaryKey[0])) {
130
                $condition = [$primaryKey[0] => $condition];
131
            } else {
132
                throw new InvalidConfigException('"' . get_called_class() . '" must have a primary key.');
133
            }
134
        }
135
136
        return $query->andWhere($condition);
137
    }
138
139
    /**
140
     * Updates the whole table using the provided attribute values and conditions.
141
     * For example, to change the status to be 1 for all customers whose status is 2:
142
     *
143
     * ```php
144
     * Customer::updateAll(['status' => 1], 'status = 2');
145
     * ```
146
     *
147
     * @param array $attributes attribute values (name-value pairs) to be saved into the table
148
     * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
149
     * Please refer to [[Query::where()]] on how to specify this parameter.
150
     * @return integer the number of rows updated
151
     * @throws NotSupportedException if not overridden
152
     */
153
    public static function updateAll($attributes, $condition = '')
154
    {
155
        throw new NotSupportedException(__METHOD__ . ' is not supported.');
156
    }
157
158
    /**
159
     * Updates the whole table using the provided counter changes and conditions.
160
     * For example, to increment all customers' age by 1,
161
     *
162
     * ```php
163
     * Customer::updateAllCounters(['age' => 1]);
164
     * ```
165
     *
166
     * @param array $counters the counters to be updated (attribute name => increment value).
167
     * Use negative values if you want to decrement the counters.
168
     * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
169
     * Please refer to [[Query::where()]] on how to specify this parameter.
170
     * @return integer the number of rows updated
171
     * @throws NotSupportedException if not overrided
172
     */
173
    public static function updateAllCounters($counters, $condition = '')
174
    {
175
        throw new NotSupportedException(__METHOD__ . ' is not supported.');
176
    }
177
178
    /**
179
     * Deletes rows in the table using the provided conditions.
180
     * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
181
     *
182
     * For example, to delete all customers whose status is 3:
183
     *
184
     * ```php
185
     * Customer::deleteAll('status = 3');
186
     * ```
187
     *
188
     * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
189
     * Please refer to [[Query::where()]] on how to specify this parameter.
190
     * @param array $params the parameters (name => value) to be bound to the query.
191
     * @return integer the number of rows deleted
192
     * @throws NotSupportedException if not overrided
193
     */
194
    public static function deleteAll($condition = '', $params = [])
195
    {
196
        throw new NotSupportedException(__METHOD__ . ' is not supported.');
197
    }
198
199
    /**
200
     * Returns the name of the column that stores the lock version for implementing optimistic locking.
201
     *
202
     * Optimistic locking allows multiple users to access the same record for edits and avoids
203
     * potential conflicts. In case when a user attempts to save the record upon some staled data
204
     * (because another user has modified the data), a [[StaleObjectException]] exception will be thrown,
205
     * and the update or deletion is skipped.
206
     *
207
     * Optimistic locking is only supported by [[update()]] and [[delete()]].
208
     *
209
     * To use Optimistic locking:
210
     *
211
     * 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`.
212
     *    Override this method to return the name of this column.
213
     * 2. Add a `required` validation rule for the version column to ensure the version value is submitted.
214
     * 3. In the Web form that collects the user input, add a hidden field that stores
215
     *    the lock version of the recording being updated.
216
     * 4. In the controller action that does the data updating, try to catch the [[StaleObjectException]]
217
     *    and implement necessary business logic (e.g. merging the changes, prompting stated data)
218
     *    to resolve the conflict.
219
     *
220
     * @return string the column name that stores the lock version of a table row.
221
     * If null is returned (default implemented), optimistic locking will not be supported.
222
     */
223 22
    public function optimisticLock()
224
    {
225 22
        return null;
226
    }
227
228
    /**
229
     * PHP getter magic method.
230
     * This method is overridden so that attributes and related objects can be accessed like properties.
231
     *
232
     * @param string $name property name
233
     * @throws \yii\base\InvalidParamException if relation name is wrong
234
     * @return mixed property value
235
     * @see getAttribute()
236
     */
237 207
    public function __get($name)
238
    {
239 207
        if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
240 198
            return $this->_attributes[$name];
241 116
        } elseif ($this->hasAttribute($name)) {
242 23
            return null;
243
        } else {
244 101
            if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {
245 58
                return $this->_related[$name];
246
            }
247 73
            $value = parent::__get($name);
248 73
            if ($value instanceof ActiveQueryInterface) {
249 42
                return $this->_related[$name] = $value->findFor($name, $this);
250
            } else {
251 37
                return $value;
252
            }
253
        }
254
    }
255
256
    /**
257
     * PHP setter magic method.
258
     * This method is overridden so that AR attributes can be accessed like properties.
259
     * @param string $name property name
260
     * @param mixed $value property value
261
     */
262 89
    public function __set($name, $value)
263
    {
264 89
        if ($this->hasAttribute($name)) {
265 89
            $this->_attributes[$name] = $value;
266 89
        } else {
267
            parent::__set($name, $value);
268
        }
269 89
    }
270
271
    /**
272
     * Checks if a property value is null.
273
     * This method overrides the parent implementation by checking if the named attribute is null or not.
274
     * @param string $name the property name or the event name
275
     * @return boolean whether the property value is null
276
     */
277 26
    public function __isset($name)
278
    {
279
        try {
280 26
            return $this->__get($name) !== null;
281
        } catch (\Exception $e) {
282
            return false;
283
        }
284
    }
285
286
    /**
287
     * Sets a component property to be null.
288
     * This method overrides the parent implementation by clearing
289
     * the specified attribute value.
290
     * @param string $name the property name or the event name
291
     */
292 6
    public function __unset($name)
293
    {
294 6
        if ($this->hasAttribute($name)) {
295
            unset($this->_attributes[$name]);
296 6
        } elseif (array_key_exists($name, $this->_related)) {
297 6
            unset($this->_related[$name]);
298 6
        } elseif ($this->getRelation($name, false) === null) {
299
            parent::__unset($name);
300
        }
301 6
    }
302
303
    /**
304
     * Declares a `has-one` relation.
305
     * The declaration is returned in terms of a relational [[ActiveQuery]] instance
306
     * through which the related record can be queried and retrieved back.
307
     *
308
     * A `has-one` relation means that there is at most one related record matching
309
     * the criteria set by this relation, e.g., a customer has one country.
310
     *
311
     * For example, to declare the `country` relation for `Customer` class, we can write
312
     * the following code in the `Customer` class:
313
     *
314
     * ```php
315
     * public function getCountry()
316
     * {
317
     *     return $this->hasOne(Country::className(), ['id' => 'country_id']);
318
     * }
319
     * ```
320
     *
321
     * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name
322
     * in the related class `Country`, while the 'country_id' value refers to an attribute name
323
     * in the current AR class.
324
     *
325
     * Call methods declared in [[ActiveQuery]] to further customize the relation.
326
     *
327
     * @param string $class the class name of the related record
328
     * @param array $link the primary-foreign key constraint. The keys of the array refer to
329
     * the attributes of the record associated with the `$class` model, while the values of the
330
     * array refer to the corresponding attributes in **this** AR class.
331
     * @return ActiveQueryInterface the relational query object.
332
     */
333 21
    public function hasOne($class, $link)
334
    {
335
        /* @var $class ActiveRecordInterface */
336
        /* @var $query ActiveQuery */
337 21
        $query = $class::find();
338 21
        $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...
339 21
        $query->link = $link;
340 21
        $query->multiple = false;
341 21
        return $query;
342
    }
343
344
    /**
345
     * Declares a `has-many` relation.
346
     * The declaration is returned in terms of a relational [[ActiveQuery]] instance
347
     * through which the related record can be queried and retrieved back.
348
     *
349
     * A `has-many` relation means that there are multiple related records matching
350
     * the criteria set by this relation, e.g., a customer has many orders.
351
     *
352
     * For example, to declare the `orders` relation for `Customer` class, we can write
353
     * the following code in the `Customer` class:
354
     *
355
     * ```php
356
     * public function getOrders()
357
     * {
358
     *     return $this->hasMany(Order::className(), ['customer_id' => 'id']);
359
     * }
360
     * ```
361
     *
362
     * Note that in the above, the 'customer_id' key in the `$link` parameter refers to
363
     * an attribute name in the related class `Order`, while the 'id' value refers to
364
     * an attribute name in the current AR class.
365
     *
366
     * Call methods declared in [[ActiveQuery]] to further customize the relation.
367
     *
368
     * @param string $class the class name of the related record
369
     * @param array $link the primary-foreign key constraint. The keys of the array refer to
370
     * the attributes of the record associated with the `$class` model, while the values of the
371
     * array refer to the corresponding attributes in **this** AR class.
372
     * @return ActiveQueryInterface the relational query object.
373
     */
374 94
    public function hasMany($class, $link)
375
    {
376
        /* @var $class ActiveRecordInterface */
377
        /* @var $query ActiveQuery */
378 94
        $query = $class::find();
379 94
        $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...
380 94
        $query->link = $link;
381 94
        $query->multiple = true;
382 94
        return $query;
383
    }
384
385
    /**
386
     * Populates the named relation with the related records.
387
     * Note that this method does not check if the relation exists or not.
388
     * @param string $name the relation name (case-sensitive)
389
     * @param ActiveRecordInterface|array|null $records the related records to be populated into the relation.
390
     */
391 73
    public function populateRelation($name, $records)
392
    {
393 73
        $this->_related[$name] = $records;
394 73
    }
395
396
    /**
397
     * Check whether the named relation has been populated with records.
398
     * @param string $name the relation name (case-sensitive)
399
     * @return boolean whether relation has been populated with records.
400
     */
401 37
    public function isRelationPopulated($name)
402
    {
403 37
        return array_key_exists($name, $this->_related);
404
    }
405
406
    /**
407
     * Returns all populated related records.
408
     * @return array an array of related records indexed by relation names.
409
     */
410 6
    public function getRelatedRecords()
411
    {
412 6
        return $this->_related;
413
    }
414
415
    /**
416
     * Returns a value indicating whether the model has an attribute with the specified name.
417
     * @param string $name the name of the attribute
418
     * @return boolean whether the model has an attribute with the specified name.
419
     */
420 164
    public function hasAttribute($name)
421
    {
422 164
        return isset($this->_attributes[$name]) || in_array($name, $this->attributes());
423
    }
424
425
    /**
426
     * Returns the named attribute value.
427
     * If this record is the result of a query and the attribute is not loaded,
428
     * null will be returned.
429
     * @param string $name the attribute name
430
     * @return mixed the attribute value. Null if the attribute is not set or does not exist.
431
     * @see hasAttribute()
432
     */
433
    public function getAttribute($name)
434
    {
435
        return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
436
    }
437
438
    /**
439
     * Sets the named attribute value.
440
     * @param string $name the attribute name
441
     * @param mixed $value the attribute value.
442
     * @throws InvalidParamException if the named attribute does not exist.
443
     * @see hasAttribute()
444
     */
445 44
    public function setAttribute($name, $value)
446
    {
447 44
        if ($this->hasAttribute($name)) {
448 44
            $this->_attributes[$name] = $value;
449 44
        } else {
450
            throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
451
        }
452 44
    }
453
454
    /**
455
     * Returns the old attribute values.
456
     * @return array the old attribute values (name-value pairs)
457
     */
458
    public function getOldAttributes()
459
    {
460
        return $this->_oldAttributes === null ? [] : $this->_oldAttributes;
461
    }
462
463
    /**
464
     * Sets the old attribute values.
465
     * All existing old attribute values will be discarded.
466
     * @param array|null $values old attribute values to be set.
467
     * If set to `null` this record is considered to be [[isNewRecord|new]].
468
     */
469 53
    public function setOldAttributes($values)
470
    {
471 53
        $this->_oldAttributes = $values;
472 53
    }
473
474
    /**
475
     * Returns the old value of the named attribute.
476
     * If this record is the result of a query and the attribute is not loaded,
477
     * null will be returned.
478
     * @param string $name the attribute name
479
     * @return mixed the old attribute value. Null if the attribute is not loaded before
480
     * or does not exist.
481
     * @see hasAttribute()
482
     */
483
    public function getOldAttribute($name)
484
    {
485
        return isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
486
    }
487
488
    /**
489
     * Sets the old value of the named attribute.
490
     * @param string $name the attribute name
491
     * @param mixed $value the old attribute value.
492
     * @throws InvalidParamException if the named attribute does not exist.
493
     * @see hasAttribute()
494
     */
495
    public function setOldAttribute($name, $value)
496
    {
497
        if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) {
498
            $this->_oldAttributes[$name] = $value;
499
        } else {
500
            throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
501
        }
502
    }
503
504
    /**
505
     * Marks an attribute dirty.
506
     * This method may be called to force updating a record when calling [[update()]],
507
     * even if there is no change being made to the record.
508
     * @param string $name the attribute name
509
     */
510
    public function markAttributeDirty($name)
511
    {
512
        unset($this->_oldAttributes[$name]);
513
    }
514
515
    /**
516
     * Returns a value indicating whether the named attribute has been changed.
517
     * @param string $name the name of the attribute.
518
     * @param boolean $identical whether the comparison of new and old value is made for
519
     * identical values using `===`, defaults to `true`. Otherwise `==` is used for comparison.
520
     * This parameter is available since version 2.0.4.
521
     * @return boolean whether the attribute has been changed
522
     */
523 1
    public function isAttributeChanged($name, $identical = true)
524
    {
525 1
        if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) {
526 1
            if ($identical) {
527 1
                return $this->_attributes[$name] !== $this->_oldAttributes[$name];
528
            } else {
529
                return $this->_attributes[$name] != $this->_oldAttributes[$name];
530
            }
531
        } else {
532
            return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]);
533
        }
534
    }
535
536
    /**
537
     * Returns the attribute values that have been modified since they are loaded or saved most recently.
538
     *
539
     * The comparison of new and old values is made for identical values using `===`.
540
     *
541
     * @param string[]|null $names the names of the attributes whose values may be returned if they are
542
     * changed recently. If null, [[attributes()]] will be used.
543
     * @return array the changed attribute values (name-value pairs)
544
     */
545 63
    public function getDirtyAttributes($names = null)
546
    {
547 63
        if ($names === null) {
548 60
            $names = $this->attributes();
549 60
        }
550 63
        $names = array_flip($names);
551 63
        $attributes = [];
552 63
        if ($this->_oldAttributes === null) {
553 47
            foreach ($this->_attributes as $name => $value) {
554 44
                if (isset($names[$name])) {
555 44
                    $attributes[$name] = $value;
556 44
                }
557 47
            }
558 47
        } else {
559 25
            foreach ($this->_attributes as $name => $value) {
560 25
                if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) {
561 25
                    $attributes[$name] = $value;
562 25
                }
563 25
            }
564
        }
565 63
        return $attributes;
566
    }
567
568
    /**
569
     * Saves the current record.
570
     *
571
     * This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]]
572
     * when [[isNewRecord]] is false.
573
     *
574
     * For example, to save a customer record:
575
     *
576
     * ```php
577
     * $customer = new Customer; // or $customer = Customer::findOne($id);
578
     * $customer->name = $name;
579
     * $customer->email = $email;
580
     * $customer->save();
581
     * ```
582
     *
583
     * @param boolean $runValidation whether to perform validation (calling [[validate()]])
584
     * before saving the record. Defaults to `true`. If the validation fails, the record
585
     * will not be saved to the database and this method will return `false`.
586
     * @param array $attributeNames list of attribute names that need to be saved. Defaults to null,
587
     * meaning all attributes that are loaded from DB will be saved.
588
     * @return boolean whether the saving succeeded (i.e. no validation errors occurred).
589
     */
590 60
    public function save($runValidation = true, $attributeNames = null)
591
    {
592 60
        if ($this->getIsNewRecord()) {
593 47
            return $this->insert($runValidation, $attributeNames);
594
        } else {
595 22
            return $this->update($runValidation, $attributeNames) !== false;
596
        }
597
    }
598
599
    /**
600
     * Saves the changes to this active record into the associated database table.
601
     *
602
     * This method performs the following steps in order:
603
     *
604
     * 1. call [[beforeValidate()]] when `$runValidation` is true. If [[beforeValidate()]]
605
     *    returns `false`, the rest of the steps will be skipped;
606
     * 2. call [[afterValidate()]] when `$runValidation` is true. If validation
607
     *    failed, the rest of the steps will be skipped;
608
     * 3. call [[beforeSave()]]. If [[beforeSave()]] returns `false`,
609
     *    the rest of the steps will be skipped;
610
     * 4. save the record into database. If this fails, it will skip the rest of the steps;
611
     * 5. call [[afterSave()]];
612
     *
613
     * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
614
     * [[EVENT_AFTER_VALIDATE]], [[EVENT_BEFORE_UPDATE]], and [[EVENT_AFTER_UPDATE]]
615
     * will be raised by the corresponding methods.
616
     *
617
     * Only the [[dirtyAttributes|changed attribute values]] will be saved into database.
618
     *
619
     * For example, to update a customer record:
620
     *
621
     * ```php
622
     * $customer = Customer::findOne($id);
623
     * $customer->name = $name;
624
     * $customer->email = $email;
625
     * $customer->update();
626
     * ```
627
     *
628
     * Note that it is possible the update does not affect any row in the table.
629
     * In this case, this method will return 0. For this reason, you should use the following
630
     * code to check if update() is successful or not:
631
     *
632
     * ```php
633
     * if ($customer->update() !== false) {
634
     *     // update successful
635
     * } else {
636
     *     // update failed
637
     * }
638
     * ```
639
     *
640
     * @param boolean $runValidation whether to perform validation (calling [[validate()]])
641
     * before saving the record. Defaults to `true`. If the validation fails, the record
642
     * will not be saved to the database and this method will return `false`.
643
     * @param array $attributeNames list of attribute names that need to be saved. Defaults to null,
644
     * meaning all attributes that are loaded from DB will be saved.
645
     * @return integer|boolean the number of rows affected, or false if validation fails
646
     * or [[beforeSave()]] stops the updating process.
647
     * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
648
     * being updated is outdated.
649
     * @throws Exception in case update failed.
650
     */
651
    public function update($runValidation = true, $attributeNames = null)
652
    {
653
        if ($runValidation && !$this->validate($attributeNames)) {
654
            return false;
655
        }
656
        return $this->updateInternal($attributeNames);
657
    }
658
659
    /**
660
     * Updates the specified attributes.
661
     *
662
     * This method is a shortcut to [[update()]] when data validation is not needed
663
     * and only a small set attributes need to be updated.
664
     *
665
     * You may specify the attributes to be updated as name list or name-value pairs.
666
     * If the latter, the corresponding attribute values will be modified accordingly.
667
     * The method will then save the specified attributes into database.
668
     *
669
     * Note that this method will **not** perform data validation and will **not** trigger events.
670
     *
671
     * @param array $attributes the attributes (names or name-value pairs) to be updated
672
     * @return integer the number of rows affected.
673
     */
674 3
    public function updateAttributes($attributes)
675
    {
676 3
        $attrs = [];
677 3
        foreach ($attributes as $name => $value) {
678 3
            if (is_int($name)) {
679 3
                $attrs[] = $value;
680 3
            } else {
681 3
                $this->$name = $value;
682 3
                $attrs[] = $name;
683
            }
684 3
        }
685
686 3
        $values = $this->getDirtyAttributes($attrs);
687 3
        if (empty($values)) {
688
            return 0;
689
        }
690
691 3
        $rows = static::updateAll($values, $this->getOldPrimaryKey(true));
692
693 3
        foreach ($values as $name => $value) {
694 3
            $this->_oldAttributes[$name] = $this->_attributes[$name];
695 3
        }
696
697 3
        return $rows;
698
    }
699
700
    /**
701
     * @see update()
702
     * @param array $attributes attributes to update
703
     * @return integer number of rows updated
704
     * @throws StaleObjectException
705
     */
706 22
    protected function updateInternal($attributes = null)
707
    {
708 22
        if (!$this->beforeSave(false)) {
709
            return false;
710
        }
711 22
        $values = $this->getDirtyAttributes($attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 706 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...
712 22
        if (empty($values)) {
713 1
            $this->afterSave(false, $values);
714 1
            return 0;
715
        }
716 22
        $condition = $this->getOldPrimaryKey(true);
717 22
        $lock = $this->optimisticLock();
718 22
        if ($lock !== null) {
719 3
            $values[$lock] = $this->$lock + 1;
720 3
            $condition[$lock] = $this->$lock;
721 3
        }
722
        // We do not check the return value of updateAll() because it's possible
723
        // that the UPDATE statement doesn't change anything and thus returns 0.
724 22
        $rows = static::updateAll($values, $condition);
725
726 22
        if ($lock !== null && !$rows) {
727 3
            throw new StaleObjectException('The object being updated is outdated.');
728
        }
729
730 22
        if (isset($values[$lock])) {
731 3
            $this->$lock = $values[$lock];
732 3
        }
733
734 22
        $changedAttributes = [];
735 22
        foreach ($values as $name => $value) {
736 22
            $changedAttributes[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
737 22
            $this->_oldAttributes[$name] = $value;
738 22
        }
739 22
        $this->afterSave(false, $changedAttributes);
740
741 22
        return $rows;
742
    }
743
744
    /**
745
     * Updates one or several counter columns for the current AR object.
746
     * Note that this method differs from [[updateAllCounters()]] in that it only
747
     * saves counters for the current AR object.
748
     *
749
     * An example usage is as follows:
750
     *
751
     * ```php
752
     * $post = Post::findOne($id);
753
     * $post->updateCounters(['view_count' => 1]);
754
     * ```
755
     *
756
     * @param array $counters the counters to be updated (attribute name => increment value)
757
     * Use negative values if you want to decrement the counters.
758
     * @return boolean whether the saving is successful
759
     * @see updateAllCounters()
760
     */
761 6
    public function updateCounters($counters)
762
    {
763 6
        if (static::updateAllCounters($counters, $this->getOldPrimaryKey(true)) > 0) {
764 6
            foreach ($counters as $name => $value) {
765 6
                if (!isset($this->_attributes[$name])) {
766 3
                    $this->_attributes[$name] = $value;
767 3
                } else {
768 3
                    $this->_attributes[$name] += $value;
769
                }
770 6
                $this->_oldAttributes[$name] = $this->_attributes[$name];
771 6
            }
772 6
            return true;
773
        } else {
774
            return false;
775
        }
776
    }
777
778
    /**
779
     * Deletes the table row corresponding to this active record.
780
     *
781
     * This method performs the following steps in order:
782
     *
783
     * 1. call [[beforeDelete()]]. If the method returns false, it will skip the
784
     *    rest of the steps;
785
     * 2. delete the record from the database;
786
     * 3. call [[afterDelete()]].
787
     *
788
     * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
789
     * will be raised by the corresponding methods.
790
     *
791
     * @return integer|false the number of rows deleted, or false if the deletion is unsuccessful for some reason.
792
     * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
793
     * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
794
     * being deleted is outdated.
795
     * @throws Exception in case delete failed.
796
     */
797
    public function delete()
798
    {
799
        $result = false;
800
        if ($this->beforeDelete()) {
801
            // we do not check the return value of deleteAll() because it's possible
802
            // the record is already deleted in the database and thus the method will return 0
803
            $condition = $this->getOldPrimaryKey(true);
804
            $lock = $this->optimisticLock();
805
            if ($lock !== null) {
806
                $condition[$lock] = $this->$lock;
807
            }
808
            $result = static::deleteAll($condition);
809
            if ($lock !== null && !$result) {
810
                throw new StaleObjectException('The object being deleted is outdated.');
811
            }
812
            $this->_oldAttributes = null;
813
            $this->afterDelete();
814
        }
815
816
        return $result;
817
    }
818
819
    /**
820
     * Returns a value indicating whether the current record is new.
821
     * @return boolean whether the record is new and should be inserted when calling [[save()]].
822
     */
823 87
    public function getIsNewRecord()
824
    {
825 87
        return $this->_oldAttributes === null;
826
    }
827
828
    /**
829
     * Sets the value indicating whether the record is new.
830
     * @param boolean $value whether the record is new and should be inserted when calling [[save()]].
831
     * @see getIsNewRecord()
832
     */
833
    public function setIsNewRecord($value)
834
    {
835
        $this->_oldAttributes = $value ? null : $this->_attributes;
836
    }
837
838
    /**
839
     * Initializes the object.
840
     * This method is called at the end of the constructor.
841
     * The default implementation will trigger an [[EVENT_INIT]] event.
842
     * If you override this method, make sure you call the parent implementation at the end
843
     * to ensure triggering of the event.
844
     */
845 223
    public function init()
846
    {
847 223
        parent::init();
848 223
        $this->trigger(self::EVENT_INIT);
849 223
    }
850
851
    /**
852
     * This method is called when the AR object is created and populated with the query result.
853
     * The default implementation will trigger an [[EVENT_AFTER_FIND]] event.
854
     * When overriding this method, make sure you call the parent implementation to ensure the
855
     * event is triggered.
856
     */
857 191
    public function afterFind()
858
    {
859 191
        $this->trigger(self::EVENT_AFTER_FIND);
860 191
    }
861
862
    /**
863
     * This method is called at the beginning of inserting or updating a record.
864
     * The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is true,
865
     * or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is false.
866
     * When overriding this method, make sure you call the parent implementation like the following:
867
     *
868
     * ```php
869
     * public function beforeSave($insert)
870
     * {
871
     *     if (parent::beforeSave($insert)) {
872
     *         // ...custom code here...
873
     *         return true;
874
     *     } else {
875
     *         return false;
876
     *     }
877
     * }
878
     * ```
879
     *
880
     * @param boolean $insert whether this method called while inserting a record.
881
     * If false, it means the method is called while updating a record.
882
     * @return boolean whether the insertion or updating should continue.
883
     * If false, the insertion or updating will be cancelled.
884
     */
885 60
    public function beforeSave($insert)
886
    {
887 60
        $event = new ModelEvent;
888 60
        $this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event);
889
890 60
        return $event->isValid;
891
    }
892
893
    /**
894
     * This method is called at the end of inserting or updating a record.
895
     * The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is true,
896
     * or an [[EVENT_AFTER_UPDATE]] event if `$insert` is false. The event class used is [[AfterSaveEvent]].
897
     * When overriding this method, make sure you call the parent implementation so that
898
     * the event is triggered.
899
     * @param boolean $insert whether this method called while inserting a record.
900
     * If false, it means the method is called while updating a record.
901
     * @param array $changedAttributes The old values of attributes that had changed and were saved.
902
     * You can use this parameter to take action based on the changes made for example send an email
903
     * when the password had changed or implement audit trail that tracks all the changes.
904
     * `$changedAttributes` gives you the old attribute values while the active record (`$this`) has
905
     * already the new, updated values.
906
     */
907 60
    public function afterSave($insert, $changedAttributes)
908
    {
909 60
        $this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE, new AfterSaveEvent([
910
            'changedAttributes' => $changedAttributes
911 60
        ]));
912 60
    }
913
914
    /**
915
     * This method is invoked before deleting a record.
916
     * The default implementation raises the [[EVENT_BEFORE_DELETE]] event.
917
     * When overriding this method, make sure you call the parent implementation like the following:
918
     *
919
     * ```php
920
     * public function beforeDelete()
921
     * {
922
     *     if (parent::beforeDelete()) {
923
     *         // ...custom code here...
924
     *         return true;
925
     *     } else {
926
     *         return false;
927
     *     }
928
     * }
929
     * ```
930
     *
931
     * @return boolean whether the record should be deleted. Defaults to true.
932
     */
933 6
    public function beforeDelete()
934
    {
935 6
        $event = new ModelEvent;
936 6
        $this->trigger(self::EVENT_BEFORE_DELETE, $event);
937
938 6
        return $event->isValid;
939
    }
940
941
    /**
942
     * This method is invoked after deleting a record.
943
     * The default implementation raises the [[EVENT_AFTER_DELETE]] event.
944
     * You may override this method to do postprocessing after the record is deleted.
945
     * Make sure you call the parent implementation so that the event is raised properly.
946
     */
947 6
    public function afterDelete()
948
    {
949 6
        $this->trigger(self::EVENT_AFTER_DELETE);
950 6
    }
951
952
    /**
953
     * Repopulates this active record with the latest data.
954
     * @return boolean whether the row still exists in the database. If true, the latest data
955
     * will be populated to this active record. Otherwise, this record will remain unchanged.
956
     */
957 14
    public function refresh()
958
    {
959
        /* @var $record BaseActiveRecord */
960 14
        $record = static::findOne($this->getPrimaryKey(true));
961 14
        if ($record === null) {
962 3
            return false;
963
        }
964 14
        foreach ($this->attributes() as $name) {
965 14
            $this->_attributes[$name] = isset($record->_attributes[$name]) ? $record->_attributes[$name] : null;
966 14
        }
967 14
        $this->_oldAttributes = $this->_attributes;
968 14
        $this->_related = [];
969
970 14
        return true;
971
    }
972
973
    /**
974
     * Returns a value indicating whether the given active record is the same as the current one.
975
     * The comparison is made by comparing the table names and the primary key values of the two active records.
976
     * If one of the records [[isNewRecord|is new]] they are also considered not equal.
977
     * @param ActiveRecordInterface $record record to compare to
978
     * @return boolean whether the two active records refer to the same row in the same database table.
979
     */
980
    public function equals($record)
981
    {
982
        if ($this->getIsNewRecord() || $record->getIsNewRecord()) {
983
            return false;
984
        }
985
986
        return get_class($this) === get_class($record) && $this->getPrimaryKey() === $record->getPrimaryKey();
987
    }
988
989
    /**
990
     * Returns the primary key value(s).
991
     * @param boolean $asArray whether to return the primary key value as an array. If true,
992
     * the return value will be an array with column names as keys and column values as values.
993
     * Note that for composite primary keys, an array will always be returned regardless of this parameter value.
994
     * @property mixed The primary key value. An array (column name => column value) is returned if
995
     * the primary key is composite. A string is returned otherwise (null will be returned if
996
     * the key value is null).
997
     * @return mixed the primary key value. An array (column name => column value) is returned if the primary key
998
     * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
999
     * the key value is null).
1000
     */
1001 40
    public function getPrimaryKey($asArray = false)
1002
    {
1003 40
        $keys = $this->primaryKey();
1004 40
        if (!$asArray && count($keys) === 1) {
1005 23
            return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null;
1006
        } else {
1007 17
            $values = [];
1008 17
            foreach ($keys as $name) {
1009 17
                $values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
1010 17
            }
1011
1012 17
            return $values;
1013
        }
1014
    }
1015
1016
    /**
1017
     * Returns the old primary key value(s).
1018
     * This refers to the primary key value that is populated into the record
1019
     * after executing a find method (e.g. find(), findOne()).
1020
     * The value remains unchanged even if the primary key attribute is manually assigned with a different value.
1021
     * @param boolean $asArray whether to return the primary key value as an array. If true,
1022
     * the return value will be an array with column name as key and column value as value.
1023
     * If this is false (default), a scalar value will be returned for non-composite primary key.
1024
     * @property mixed The old primary key value. An array (column name => column value) is
1025
     * returned if the primary key is composite. A string is returned otherwise (null will be
1026
     * returned if the key value is null).
1027
     * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key
1028
     * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
1029
     * the key value is null).
1030
     * @throws Exception if the AR model does not have a primary key
1031
     */
1032 43
    public function getOldPrimaryKey($asArray = false)
1033
    {
1034 43
        $keys = $this->primaryKey();
1035 43
        if (empty($keys)) {
1036
            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.');
1037
        }
1038 43
        if (!$asArray && count($keys) === 1) {
1039 7
            return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null;
1040
        } else {
1041 37
            $values = [];
1042 37
            foreach ($keys as $name) {
1043 37
                $values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
1044 37
            }
1045
1046 37
            return $values;
1047
        }
1048
    }
1049
1050
    /**
1051
     * Populates an active record object using a row of data from the database/storage.
1052
     *
1053
     * This is an internal method meant to be called to create active record objects after
1054
     * fetching data from the database. It is mainly used by [[ActiveQuery]] to populate
1055
     * the query results into active records.
1056
     *
1057
     * When calling this method manually you should call [[afterFind()]] on the created
1058
     * record to trigger the [[EVENT_AFTER_FIND|afterFind Event]].
1059
     *
1060
     * @param BaseActiveRecord $record the record to be populated. In most cases this will be an instance
1061
     * created by [[instantiate()]] beforehand.
1062
     * @param array $row attribute values (name => value)
1063
     */
1064 191
    public static function populateRecord($record, $row)
1065
    {
1066 191
        $columns = array_flip($record->attributes());
1067 191
        foreach ($row as $name => $value) {
1068 191
            if (isset($columns[$name])) {
1069 191
                $record->_attributes[$name] = $value;
1070 191
            } elseif ($record->canSetProperty($name)) {
1071 6
                $record->$name = $value;
1072 6
            }
1073 191
        }
1074 191
        $record->_oldAttributes = $record->_attributes;
1075 191
    }
1076
1077
    /**
1078
     * Creates an active record instance.
1079
     *
1080
     * This method is called together with [[populateRecord()]] by [[ActiveQuery]].
1081
     * It is not meant to be used for creating new records directly.
1082
     *
1083
     * You may override this method if the instance being created
1084
     * depends on the row data to be populated into the record.
1085
     * For example, by creating a record based on the value of a column,
1086
     * you may implement the so-called single-table inheritance mapping.
1087
     * @param array $row row data to be populated into the record.
1088
     * @return static the newly created active record
1089
     */
1090 188
    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...
1091
    {
1092 188
        return new static;
1093
    }
1094
1095
    /**
1096
     * Returns whether there is an element at the specified offset.
1097
     * This method is required by the interface [[\ArrayAccess]].
1098
     * @param mixed $offset the offset to check on
1099
     * @return boolean whether there is an element at the specified offset.
1100
     */
1101 18
    public function offsetExists($offset)
1102
    {
1103 18
        return $this->__isset($offset);
1104
    }
1105
1106
    /**
1107
     * Returns the relation object with the specified name.
1108
     * A relation is defined by a getter method which returns an [[ActiveQueryInterface]] object.
1109
     * It can be declared in either the Active Record class itself or one of its behaviors.
1110
     * @param string $name the relation name
1111
     * @param boolean $throwException whether to throw exception if the relation does not exist.
1112
     * @return ActiveQueryInterface|ActiveQuery the relational query object. If the relation does not exist
1113
     * and `$throwException` is false, null will be returned.
1114
     * @throws InvalidParamException if the named relation does not exist.
1115
     */
1116 85
    public function getRelation($name, $throwException = true)
1117
    {
1118 85
        $getter = 'get' . $name;
1119
        try {
1120
            // the relation could be defined in a behavior
1121 85
            $relation = $this->$getter();
1122 85
        } catch (UnknownMethodException $e) {
1123
            if ($throwException) {
1124
                throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
1125
            } else {
1126
                return null;
1127
            }
1128
        }
1129 85
        if (!$relation instanceof ActiveQueryInterface) {
1130
            if ($throwException) {
1131
                throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".');
1132
            } else {
1133
                return null;
1134
            }
1135
        }
1136
1137 85
        if (method_exists($this, $getter)) {
1138
            // relation name is case sensitive, trying to validate it when the relation is defined within this class
1139 85
            $method = new \ReflectionMethod($this, $getter);
1140 85
            $realName = lcfirst(substr($method->getName(), 3));
1141 85
            if ($realName !== $name) {
1142
                if ($throwException) {
1143
                    throw new InvalidParamException('Relation names are case sensitive. ' . get_class($this) . " has a relation named \"$realName\" instead of \"$name\".");
1144
                } else {
1145
                    return null;
1146
                }
1147
            }
1148 85
        }
1149
1150 85
        return $relation;
1151
    }
1152
1153
    /**
1154
     * Establishes the relationship between two models.
1155
     *
1156
     * The relationship is established by setting the foreign key value(s) in one model
1157
     * to be the corresponding primary key value(s) in the other model.
1158
     * The model with the foreign key will be saved into database without performing validation.
1159
     *
1160
     * If the relationship involves a junction table, a new row will be inserted into the
1161
     * junction table which contains the primary key values from both models.
1162
     *
1163
     * Note that this method requires that the primary key value is not null.
1164
     *
1165
     * @param string $name the case sensitive name of the relationship.
1166
     * @param ActiveRecordInterface $model the model to be linked with the current one.
1167
     * @param array $extraColumns additional column values to be saved into the junction table.
1168
     * This parameter is only meaningful for a relationship involving a junction table
1169
     * (i.e., a relation set with [[ActiveRelationTrait::via()]] or [[ActiveQuery::viaTable()]].)
1170
     * @throws InvalidCallException if the method is unable to link two models.
1171
     */
1172 3
    public function link($name, $model, $extraColumns = [])
1173
    {
1174 3
        $relation = $this->getRelation($name);
1175
1176 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...
1177 3
            if ($this->getIsNewRecord() || $model->getIsNewRecord()) {
1178
                throw new InvalidCallException('Unable to link models: the models being linked cannot be newly created.');
1179
            }
1180 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...
1181
                /* @var $viaRelation ActiveQuery */
1182 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...
1183 3
                $viaClass = $viaRelation->modelClass;
1184
                // unset $viaName so that it can be reloaded to reflect the change
1185 3
                unset($this->_related[$viaName]);
1186 3
            } else {
1187
                $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...
1188
                $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...
1189
            }
1190 3
            $columns = [];
1191 3
            foreach ($viaRelation->link as $a => $b) {
1192 3
                $columns[$a] = $this->$b;
1193 3
            }
1194 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...
1195 3
                $columns[$b] = $model->$a;
1196 3
            }
1197 3
            foreach ($extraColumns as $k => $v) {
1198 3
                $columns[$k] = $v;
1199 3
            }
1200 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...
1201
                /* @var $viaClass ActiveRecordInterface */
1202
                /* @var $record ActiveRecordInterface */
1203 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...
1204 3
                foreach ($columns as $column => $value) {
1205 3
                    $record->$column = $value;
1206 3
                }
1207 3
                $record->insert(false);
1208 3
            } else {
1209
                /* @var $viaTable string */
1210
                static::getDb()->createCommand()
1211
                    ->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...
1212
            }
1213 3
        } else {
1214 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...
1215 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...
1216 3
            if ($p1 && $p2) {
1217
                if ($this->getIsNewRecord() && $model->getIsNewRecord()) {
1218
                    throw new InvalidCallException('Unable to link models: at most one model can be newly created.');
1219
                } elseif ($this->getIsNewRecord()) {
1220
                    $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...
1221
                } else {
1222
                    $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...
1223
                }
1224 3
            } elseif ($p1) {
1225 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...
1226 3
            } elseif ($p2) {
1227 3
                $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...
1228 3
            } else {
1229
                throw new InvalidCallException('Unable to link models: the link defining the relation does not involve any primary key.');
1230
            }
1231
        }
1232
1233
        // update lazily loaded related objects
1234 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...
1235 3
            $this->_related[$name] = $model;
1236 3
        } elseif (isset($this->_related[$name])) {
1237 3
            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...
1238
                $indexBy = $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...
1239
                $this->_related[$name][$model->$indexBy] = $model;
1240
            } else {
1241 3
                $this->_related[$name][] = $model;
1242
            }
1243 3
        }
1244 3
    }
1245
1246
    /**
1247
     * Destroys the relationship between two models.
1248
     *
1249
     * The model with the foreign key of the relationship will be deleted if `$delete` is true.
1250
     * Otherwise, the foreign key will be set null and the model will be saved without validation.
1251
     *
1252
     * @param string $name the case sensitive name of the relationship.
1253
     * @param ActiveRecordInterface $model the model to be unlinked from the current one.
1254
     * You have to make sure that the model is really related with the current model as this method
1255
     * does not check this.
1256
     * @param boolean $delete whether to delete the model that contains the foreign key.
1257
     * If false, the model's foreign key will be set null and saved.
1258
     * If true, the model containing the foreign key will be deleted.
1259
     * @throws InvalidCallException if the models cannot be unlinked
1260
     */
1261 3
    public function unlink($name, $model, $delete = false)
1262
    {
1263 3
        $relation = $this->getRelation($name);
1264
1265 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...
1266 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...
1267
                /* @var $viaRelation ActiveQuery */
1268 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...
1269 3
                $viaClass = $viaRelation->modelClass;
1270 3
                unset($this->_related[$viaName]);
1271 3
            } else {
1272 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...
1273 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...
1274
            }
1275 3
            $columns = [];
1276 3
            foreach ($viaRelation->link as $a => $b) {
1277 3
                $columns[$a] = $this->$b;
1278 3
            }
1279 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...
1280 3
                $columns[$b] = $model->$a;
1281 3
            }
1282 3
            $nulls = [];
1283 3
            foreach (array_keys($columns) as $a) {
1284 3
                $nulls[$a] = null;
1285 3
            }
1286 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...
1287
                /* @var $viaClass ActiveRecordInterface */
1288 3
                if ($delete) {
1289 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...
1290 3
                } else {
1291
                    $viaClass::updateAll($nulls, $columns);
1292
                }
1293 3
            } else {
1294
                /* @var $viaTable string */
1295
                /* @var $command Command */
1296 3
                $command = static::getDb()->createCommand();
1297 3
                if ($delete) {
1298
                    $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...
1299
                } else {
1300 3
                    $command->update($viaTable, $nulls, $columns)->execute();
1301
                }
1302
            }
1303 3
        } else {
1304 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...
1305 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...
1306 3
            if ($p2) {
1307 3
                if ($delete) {
1308 3
                    $model->delete();
1309 3
                } else {
1310 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...
1311 3
                        $model->$a = null;
1312 3
                    }
1313 3
                    $model->save(false);
1314
                }
1315 3
            } elseif ($p1) {
1316
                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...
1317
                    if (is_array($this->$b)) { // relation via array valued attribute
1318
                        if (($key = array_search($model->$a, $this->$b, false)) !== false) {
1319
                            $values = $this->$b;
1320
                            unset($values[$key]);
1321
                            $this->$b = array_values($values);
1322
                        }
1323
                    } else {
1324
                        $this->$b = null;
1325
                    }
1326
                }
1327
                $delete ? $this->delete() : $this->save(false);
1328
            } else {
1329
                throw new InvalidCallException('Unable to unlink models: the link does not involve any primary key.');
1330
            }
1331
        }
1332
1333 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...
1334
            unset($this->_related[$name]);
1335 3
        } elseif (isset($this->_related[$name])) {
1336
            /* @var $b ActiveRecordInterface */
1337 3
            foreach ($this->_related[$name] as $a => $b) {
1338 3
                if ($model->getPrimaryKey() === $b->getPrimaryKey()) {
1339 3
                    unset($this->_related[$name][$a]);
1340 3
                }
1341 3
            }
1342 3
        }
1343 3
    }
1344
1345
    /**
1346
     * Destroys the relationship in current model.
1347
     *
1348
     * The model with the foreign key of the relationship will be deleted if `$delete` is true.
1349
     * Otherwise, the foreign key will be set null and the model will be saved without validation.
1350
     *
1351
     * Note that to destroy the relationship without removing records make sure your keys can be set to null
1352
     *
1353
     * @param string $name the case sensitive name of the relationship.
1354
     * @param boolean $delete whether to delete the model that contains the foreign key.
1355
     */
1356 12
    public function unlinkAll($name, $delete = false)
1357
    {
1358 12
        $relation = $this->getRelation($name);
1359
1360 12
        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...
1361 6
            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...
1362
                /* @var $viaRelation ActiveQuery */
1363 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...
1364 3
                $viaClass = $viaRelation->modelClass;
1365 3
                unset($this->_related[$viaName]);
1366 3
            } else {
1367 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...
1368 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...
1369
            }
1370 6
            $condition = [];
1371 6
            $nulls = [];
1372 6
            foreach ($viaRelation->link as $a => $b) {
1373 6
                $nulls[$a] = null;
1374 6
                $condition[$a] = $this->$b;
1375 6
            }
1376 6
            if (!empty($viaRelation->where)) {
1377
                $condition = ['and', $condition, $viaRelation->where];
1378
            }
1379 6
            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...
1380
                /* @var $viaClass ActiveRecordInterface */
1381 3
                if ($delete) {
1382 3
                    $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...
1383 3
                } else {
1384 3
                    $viaClass::updateAll($nulls, $condition);
1385
                }
1386 3
            } else {
1387
                /* @var $viaTable string */
1388
                /* @var $command Command */
1389 3
                $command = static::getDb()->createCommand();
1390 3
                if ($delete) {
1391 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...
1392 3
                } else {
1393 3
                    $command->update($viaTable, $nulls, $condition)->execute();
1394
                }
1395
            }
1396 6
        } else {
1397
            /* @var $relatedModel ActiveRecordInterface */
1398 9
            $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...
1399 9
            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...
1400
                // relation via array valued attribute
1401
                $this->$b = [];
1402
                $this->save(false);
1403
            } else {
1404 9
                $nulls = [];
1405 9
                $condition = [];
1406 9
                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...
1407 9
                    $nulls[$a] = null;
1408 9
                    $condition[$a] = $this->$b;
1409 9
                }
1410 9
                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...
1411 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...
1412 6
                }
1413 9
                if ($delete) {
1414 6
                    $relatedModel::deleteAll($condition);
1415 6
                } else {
1416 6
                    $relatedModel::updateAll($nulls, $condition);
1417
                }
1418
            }
1419
        }
1420
1421 12
        unset($this->_related[$name]);
1422 12
    }
1423
1424
    /**
1425
     * @param array $link
1426
     * @param ActiveRecordInterface $foreignModel
1427
     * @param ActiveRecordInterface $primaryModel
1428
     * @throws InvalidCallException
1429
     */
1430 3
    private function bindModels($link, $foreignModel, $primaryModel)
1431
    {
1432 3
        foreach ($link as $fk => $pk) {
1433 3
            $value = $primaryModel->$pk;
1434 3
            if ($value === null) {
1435
                throw new InvalidCallException('Unable to link models: the primary key of ' . get_class($primaryModel) . ' is null.');
1436
            }
1437 3
            if (is_array($foreignModel->$fk)) { // relation via array valued attribute
1438
                $foreignModel->$fk = array_merge($foreignModel->$fk, [$value]);
1439
            } else {
1440 3
                $foreignModel->$fk = $value;
1441
            }
1442 3
        }
1443 3
        $foreignModel->save(false);
1444 3
    }
1445
1446
    /**
1447
     * Returns a value indicating whether the given set of attributes represents the primary key for this model
1448
     * @param array $keys the set of attributes to check
1449
     * @return boolean whether the given set of attributes represents the primary key for this model
1450
     */
1451 9
    public static function isPrimaryKey($keys)
1452
    {
1453 9
        $pks = static::primaryKey();
1454 9
        if (count($keys) === count($pks)) {
1455 9
            return count(array_intersect($keys, $pks)) === count($pks);
1456
        } else {
1457 3
            return false;
1458
        }
1459
    }
1460
1461
    /**
1462
     * Returns the text label for the specified attribute.
1463
     * If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model.
1464
     * @param string $attribute the attribute name
1465
     * @return string the attribute label
1466
     * @see generateAttributeLabel()
1467
     * @see attributeLabels()
1468
     */
1469 40
    public function getAttributeLabel($attribute)
1470
    {
1471 40
        $labels = $this->attributeLabels();
1472 40
        if (isset($labels[$attribute])) {
1473 8
            return $labels[$attribute];
1474 39
        } elseif (strpos($attribute, '.')) {
1475
            $attributeParts = explode('.', $attribute);
1476
            $neededAttribute = array_pop($attributeParts);
1477
1478
            $relatedModel = $this;
1479
            foreach ($attributeParts as $relationName) {
1480
                if ($relatedModel->isRelationPopulated($relationName) && $relatedModel->$relationName instanceof self) {
1481
                    $relatedModel = $relatedModel->$relationName;
1482
                } else {
1483
                    try {
1484
                        $relation = $relatedModel->getRelation($relationName);
1485
                    } catch (InvalidParamException $e) {
1486
                        return $this->generateAttributeLabel($attribute);
1487
                    }
1488
                    $relatedModel = new $relation->modelClass;
1489
                }
1490
            }
1491
1492
            $labels = $relatedModel->attributeLabels();
1493
            if (isset($labels[$neededAttribute])) {
1494
                return $labels[$neededAttribute];
1495
            }
1496
        }
1497
1498 39
        return $this->generateAttributeLabel($attribute);
1499
    }
1500
1501
    /**
1502
     * Returns the text hint for the specified attribute.
1503
     * If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model.
1504
     * @param string $attribute the attribute name
1505
     * @return string the attribute hint
1506
     * @see attributeHints()
1507
     * @since 2.0.4
1508
     */
1509
    public function getAttributeHint($attribute)
1510
    {
1511
        $hints = $this->attributeHints();
1512
        if (isset($hints[$attribute])) {
1513
            return $hints[$attribute];
1514
        } elseif (strpos($attribute, '.')) {
1515
            $attributeParts = explode('.', $attribute);
1516
            $neededAttribute = array_pop($attributeParts);
1517
1518
            $relatedModel = $this;
1519
            foreach ($attributeParts as $relationName) {
1520
                if ($relatedModel->isRelationPopulated($relationName) && $relatedModel->$relationName instanceof self) {
1521
                    $relatedModel = $relatedModel->$relationName;
1522
                } else {
1523
                    try {
1524
                        $relation = $relatedModel->getRelation($relationName);
1525
                    } catch (InvalidParamException $e) {
1526
                        return '';
1527
                    }
1528
                    $relatedModel = new $relation->modelClass;
1529
                }
1530
            }
1531
1532
            $hints = $relatedModel->attributeHints();
1533
            if (isset($hints[$neededAttribute])) {
1534
                return $hints[$neededAttribute];
1535
            }
1536
        }
1537
        return '';
1538
    }
1539
1540
    /**
1541
     * @inheritdoc
1542
     *
1543
     * The default implementation returns the names of the columns whose values have been populated into this record.
1544
     */
1545
    public function fields()
1546
    {
1547
        $fields = array_keys($this->_attributes);
1548
1549
        return array_combine($fields, $fields);
1550
    }
1551
1552
    /**
1553
     * @inheritdoc
1554
     *
1555
     * The default implementation returns the names of the relations that have been populated into this record.
1556
     */
1557
    public function extraFields()
1558
    {
1559
        $fields = array_keys($this->getRelatedRecords());
1560
1561
        return array_combine($fields, $fields);
1562
    }
1563
1564
    /**
1565
     * Sets the element value at the specified offset to null.
1566
     * This method is required by the SPL interface [[\ArrayAccess]].
1567
     * It is implicitly called when you use something like `unset($model[$offset])`.
1568
     * @param mixed $offset the offset to unset element
1569
     */
1570 3
    public function offsetUnset($offset)
1571
    {
1572 3
        if (property_exists($this, $offset)) {
1573
            $this->$offset = null;
1574
        } else {
1575 3
            unset($this->$offset);
1576
        }
1577 3
    }
1578
}
1579