Completed
Push — 2.1 ( c952e8...98ed49 )
by Carsten
10:00
created

BaseActiveRecord::update()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 4
cp 0
cc 3
eloc 4
nc 2
nop 2
crap 12
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
     * @event Event an event that is triggered after a record is refreshed.
82
     * @since 2.0.8
83
     */
84
    const EVENT_AFTER_REFRESH = 'afterRefresh';
85
86
    /**
87
     * @var array attribute values indexed by attribute names
88
     */
89
    private $_attributes = [];
90
    /**
91
     * @var array|null old attribute values indexed by attribute names.
92
     * This is `null` if the record [[isNewRecord|is new]].
93
     */
94
    private $_oldAttributes;
95
    /**
96
     * @var array related models indexed by the relation names
97
     */
98
    private $_related = [];
99
100
101
    /**
102
     * @inheritdoc
103
     * @return static|null ActiveRecord instance matching the condition, or `null` if nothing matches.
104
     */
105 148
    public static function findOne($condition)
106
    {
107 148
        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...
108
    }
109
110
    /**
111
     * @inheritdoc
112
     * @return static[] an array of ActiveRecord instances, or an empty array if nothing matches.
113
     */
114
    public static function findAll($condition)
115
    {
116
        return static::findByCondition($condition)->all();
117
    }
118
119
    /**
120
     * Finds ActiveRecord instance(s) by the given condition.
121
     * This method is internally called by [[findOne()]] and [[findAll()]].
122
     * @param mixed $condition please refer to [[findOne()]] for the explanation of this parameter
123
     * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance.
124
     * @throws InvalidConfigException if there is no primary key defined
125
     * @internal
126
     */
127
    protected static function findByCondition($condition)
128
    {
129
        $query = static::find();
130
131
        if (!ArrayHelper::isAssociative($condition)) {
132
            // query by primary key
133
            $primaryKey = static::primaryKey();
134
            if (isset($primaryKey[0])) {
135
                $condition = [$primaryKey[0] => $condition];
136
            } else {
137
                throw new InvalidConfigException('"' . get_called_class() . '" must have a primary key.');
138
            }
139
        }
140
141
        return $query->andWhere($condition);
142
    }
143
144
    /**
145
     * Updates the whole table using the provided attribute values and conditions.
146
     * For example, to change the status to be 1 for all customers whose status is 2:
147
     *
148
     * ```php
149
     * Customer::updateAll(['status' => 1], 'status = 2');
150
     * ```
151
     *
152
     * @param array $attributes attribute values (name-value pairs) to be saved into the table
153
     * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
154
     * Please refer to [[Query::where()]] on how to specify this parameter.
155
     * @return integer the number of rows updated
156
     * @throws NotSupportedException if not overridden
157
     */
158
    public static function updateAll($attributes, $condition = '')
159
    {
160
        throw new NotSupportedException(__METHOD__ . ' is not supported.');
161
    }
162
163
    /**
164
     * Updates the whole table using the provided counter changes and conditions.
165
     * For example, to increment all customers' age by 1,
166
     *
167
     * ```php
168
     * Customer::updateAllCounters(['age' => 1]);
169
     * ```
170
     *
171
     * @param array $counters the counters to be updated (attribute name => increment value).
172
     * Use negative values if you want to decrement the counters.
173
     * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
174
     * Please refer to [[Query::where()]] on how to specify this parameter.
175
     * @return integer the number of rows updated
176
     * @throws NotSupportedException if not overrided
177
     */
178
    public static function updateAllCounters($counters, $condition = '')
179
    {
180
        throw new NotSupportedException(__METHOD__ . ' is not supported.');
181
    }
182
183
    /**
184
     * Deletes rows in the table using the provided conditions.
185
     * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
186
     *
187
     * For example, to delete all customers whose status is 3:
188
     *
189
     * ```php
190
     * Customer::deleteAll('status = 3');
191
     * ```
192
     *
193
     * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
194
     * Please refer to [[Query::where()]] on how to specify this parameter.
195
     * @param array $params the parameters (name => value) to be bound to the query.
196
     * @return integer the number of rows deleted
197
     * @throws NotSupportedException if not overrided
198
     */
199
    public static function deleteAll($condition = '', $params = [])
200
    {
201
        throw new NotSupportedException(__METHOD__ . ' is not supported.');
202
    }
203
204
    /**
205
     * Returns the name of the column that stores the lock version for implementing optimistic locking.
206
     *
207
     * Optimistic locking allows multiple users to access the same record for edits and avoids
208
     * potential conflicts. In case when a user attempts to save the record upon some staled data
209
     * (because another user has modified the data), a [[StaleObjectException]] exception will be thrown,
210
     * and the update or deletion is skipped.
211
     *
212
     * Optimistic locking is only supported by [[update()]] and [[delete()]].
213
     *
214
     * To use Optimistic locking:
215
     *
216
     * 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`.
217
     *    Override this method to return the name of this column.
218
     * 2. Add a `required` validation rule for the version column to ensure the version value is submitted.
219
     * 3. In the Web form that collects the user input, add a hidden field that stores
220
     *    the lock version of the recording being updated.
221
     * 4. In the controller action that does the data updating, try to catch the [[StaleObjectException]]
222
     *    and implement necessary business logic (e.g. merging the changes, prompting stated data)
223
     *    to resolve the conflict.
224
     *
225
     * @return string the column name that stores the lock version of a table row.
226
     * If null is returned (default implemented), optimistic locking will not be supported.
227
     */
228 22
    public function optimisticLock()
229
    {
230 22
        return null;
231
    }
232
233
    /**
234
     * PHP getter magic method.
235
     * This method is overridden so that attributes and related objects can be accessed like properties.
236
     *
237
     * @param string $name property name
238
     * @throws \yii\base\InvalidParamException if relation name is wrong
239
     * @return mixed property value
240
     * @see getAttribute()
241
     */
242 239
    public function __get($name)
243
    {
244 239
        if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
245 229
            return $this->_attributes[$name];
246 131
        } elseif ($this->hasAttribute($name)) {
247 23
            return null;
248
        } else {
249 116
            if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) {
250 64
                return $this->_related[$name];
251
            }
252 82
            $value = parent::__get($name);
253 82
            if ($value instanceof ActiveQueryInterface) {
254 48
                return $this->_related[$name] = $value->findFor($name, $this);
255
            } else {
256 40
                return $value;
257
            }
258
        }
259
    }
260
261
    /**
262
     * PHP setter magic method.
263
     * This method is overridden so that AR attributes can be accessed like properties.
264
     * @param string $name property name
265
     * @param mixed $value property value
266
     */
267 103
    public function __set($name, $value)
268
    {
269 103
        if ($this->hasAttribute($name)) {
270 103
            $this->_attributes[$name] = $value;
271 103
        } else {
272
            parent::__set($name, $value);
273
        }
274 103
    }
275
276
    /**
277
     * Checks if a property value is null.
278
     * This method overrides the parent implementation by checking if the named attribute is null or not.
279
     * @param string $name the property name or the event name
280
     * @return boolean whether the property value is null
281
     */
282 35
    public function __isset($name)
283
    {
284
        try {
285 35
            return $this->__get($name) !== null;
286
        } catch (\Exception $e) {
287
            return false;
288
        }
289
    }
290
291
    /**
292
     * Sets a component property to be null.
293
     * This method overrides the parent implementation by clearing
294
     * the specified attribute value.
295
     * @param string $name the property name or the event name
296
     */
297 6
    public function __unset($name)
298
    {
299 6
        if ($this->hasAttribute($name)) {
300
            unset($this->_attributes[$name]);
301 6
        } elseif (array_key_exists($name, $this->_related)) {
302 6
            unset($this->_related[$name]);
303 6
        } elseif ($this->getRelation($name, false) === null) {
304
            parent::__unset($name);
305
        }
306 6
    }
307
308
    /**
309
     * Declares a `has-one` relation.
310
     * The declaration is returned in terms of a relational [[ActiveQuery]] instance
311
     * through which the related record can be queried and retrieved back.
312
     *
313
     * A `has-one` relation means that there is at most one related record matching
314
     * the criteria set by this relation, e.g., a customer has one country.
315
     *
316
     * For example, to declare the `country` relation for `Customer` class, we can write
317
     * the following code in the `Customer` class:
318
     *
319
     * ```php
320
     * public function getCountry()
321
     * {
322
     *     return $this->hasOne(Country::class, ['id' => 'country_id']);
323
     * }
324
     * ```
325
     *
326
     * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name
327
     * in the related class `Country`, while the 'country_id' value refers to an attribute name
328
     * in the current AR class.
329
     *
330
     * Call methods declared in [[ActiveQuery]] to further customize the relation.
331
     *
332
     * @param string $class the class name of the related record
333
     * @param array $link the primary-foreign key constraint. The keys of the array refer to
334
     * the attributes of the record associated with the `$class` model, while the values of the
335
     * array refer to the corresponding attributes in **this** AR class.
336
     * @return ActiveQueryInterface the relational query object.
337
     */
338 39
    public function hasOne($class, $link)
339
    {
340
        /* @var $class ActiveRecordInterface */
341
        /* @var $query ActiveQuery */
342 39
        $query = $class::find();
343 39
        $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...
344 39
        $query->link = $link;
345 39
        $query->multiple = false;
346 39
        return $query;
347
    }
348
349
    /**
350
     * Declares a `has-many` relation.
351
     * The declaration is returned in terms of a relational [[ActiveQuery]] instance
352
     * through which the related record can be queried and retrieved back.
353
     *
354
     * A `has-many` relation means that there are multiple related records matching
355
     * the criteria set by this relation, e.g., a customer has many orders.
356
     *
357
     * For example, to declare the `orders` relation for `Customer` class, we can write
358
     * the following code in the `Customer` class:
359
     *
360
     * ```php
361
     * public function getOrders()
362
     * {
363
     *     return $this->hasMany(Order::class, ['customer_id' => 'id']);
364
     * }
365
     * ```
366
     *
367
     * Note that in the above, the 'customer_id' key in the `$link` parameter refers to
368
     * an attribute name in the related class `Order`, while the 'id' value refers to
369
     * an attribute name in the current AR class.
370
     *
371
     * Call methods declared in [[ActiveQuery]] to further customize the relation.
372
     *
373
     * @param string $class the class name of the related record
374
     * @param array $link the primary-foreign key constraint. The keys of the array refer to
375
     * the attributes of the record associated with the `$class` model, while the values of the
376
     * array refer to the corresponding attributes in **this** AR class.
377
     * @return ActiveQueryInterface the relational query object.
378
     */
379 115
    public function hasMany($class, $link)
380
    {
381
        /* @var $class ActiveRecordInterface */
382
        /* @var $query ActiveQuery */
383 115
        $query = $class::find();
384 115
        $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...
385 115
        $query->link = $link;
386 115
        $query->multiple = true;
387 115
        return $query;
388
    }
389
390
    /**
391
     * Populates the named relation with the related records.
392
     * Note that this method does not check if the relation exists or not.
393
     * @param string $name the relation name (case-sensitive)
394
     * @param ActiveRecordInterface|array|null $records the related records to be populated into the relation.
395
     */
396 85
    public function populateRelation($name, $records)
397
    {
398 85
        $this->_related[$name] = $records;
399 85
    }
400
401
    /**
402
     * Check whether the named relation has been populated with records.
403
     * @param string $name the relation name (case-sensitive)
404
     * @return boolean whether relation has been populated with records.
405
     */
406 40
    public function isRelationPopulated($name)
407
    {
408 40
        return array_key_exists($name, $this->_related);
409
    }
410
411
    /**
412
     * Returns all populated related records.
413
     * @return array an array of related records indexed by relation names.
414
     */
415 6
    public function getRelatedRecords()
416
    {
417 6
        return $this->_related;
418
    }
419
420
    /**
421
     * Returns a value indicating whether the model has an attribute with the specified name.
422
     * @param string $name the name of the attribute
423
     * @return boolean whether the model has an attribute with the specified name.
424
     */
425 184
    public function hasAttribute($name)
426
    {
427 184
        return isset($this->_attributes[$name]) || in_array($name, $this->attributes());
428
    }
429
430
    /**
431
     * Returns the named attribute value.
432
     * If this record is the result of a query and the attribute is not loaded,
433
     * null will be returned.
434
     * @param string $name the attribute name
435
     * @return mixed the attribute value. Null if the attribute is not set or does not exist.
436
     * @see hasAttribute()
437
     */
438
    public function getAttribute($name)
439
    {
440
        return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
441
    }
442
443
    /**
444
     * Sets the named attribute value.
445
     * @param string $name the attribute name
446
     * @param mixed $value the attribute value.
447
     * @throws InvalidParamException if the named attribute does not exist.
448
     * @see hasAttribute()
449
     */
450 51
    public function setAttribute($name, $value)
451
    {
452 51
        if ($this->hasAttribute($name)) {
453 51
            $this->_attributes[$name] = $value;
454 51
        } else {
455
            throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
456
        }
457 51
    }
458
459
    /**
460
     * Returns the old attribute values.
461
     * @return array the old attribute values (name-value pairs)
462
     */
463
    public function getOldAttributes()
464
    {
465
        return $this->_oldAttributes === null ? [] : $this->_oldAttributes;
466
    }
467
468
    /**
469
     * Sets the old attribute values.
470
     * All existing old attribute values will be discarded.
471
     * @param array|null $values old attribute values to be set.
472
     * If set to `null` this record is considered to be [[isNewRecord|new]].
473
     */
474 67
    public function setOldAttributes($values)
475
    {
476 67
        $this->_oldAttributes = $values;
477 67
    }
478
479
    /**
480
     * Returns the old value of the named attribute.
481
     * If this record is the result of a query and the attribute is not loaded,
482
     * null will be returned.
483
     * @param string $name the attribute name
484
     * @return mixed the old attribute value. Null if the attribute is not loaded before
485
     * or does not exist.
486
     * @see hasAttribute()
487
     */
488
    public function getOldAttribute($name)
489
    {
490
        return isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
491
    }
492
493
    /**
494
     * Sets the old value of the named attribute.
495
     * @param string $name the attribute name
496
     * @param mixed $value the old attribute value.
497
     * @throws InvalidParamException if the named attribute does not exist.
498
     * @see hasAttribute()
499
     */
500
    public function setOldAttribute($name, $value)
501
    {
502
        if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) {
503
            $this->_oldAttributes[$name] = $value;
504
        } else {
505
            throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
506
        }
507
    }
508
509
    /**
510
     * Marks an attribute dirty.
511
     * This method may be called to force updating a record when calling [[update()]],
512
     * even if there is no change being made to the record.
513
     * @param string $name the attribute name
514
     */
515
    public function markAttributeDirty($name)
516
    {
517
        unset($this->_oldAttributes[$name]);
518
    }
519
520
    /**
521
     * Returns a value indicating whether the named attribute has been changed.
522
     * @param string $name the name of the attribute.
523
     * @param boolean $identical whether the comparison of new and old value is made for
524
     * identical values using `===`, defaults to `true`. Otherwise `==` is used for comparison.
525
     * This parameter is available since version 2.0.4.
526
     * @return boolean whether the attribute has been changed
527
     */
528 1
    public function isAttributeChanged($name, $identical = true)
529
    {
530 1
        if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) {
531 1
            if ($identical) {
532 1
                return $this->_attributes[$name] !== $this->_oldAttributes[$name];
533
            } else {
534
                return $this->_attributes[$name] != $this->_oldAttributes[$name];
535
            }
536
        } else {
537
            return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]);
538
        }
539
    }
540
541
    /**
542
     * Returns the attribute values that have been modified since they are loaded or saved most recently.
543
     *
544
     * The comparison of new and old values is made for identical values using `===`.
545
     *
546
     * @param string[]|null $names the names of the attributes whose values may be returned if they are
547
     * changed recently. If null, [[attributes()]] will be used.
548
     * @return array the changed attribute values (name-value pairs)
549
     */
550 77
    public function getDirtyAttributes($names = null)
551
    {
552 77
        if ($names === null) {
553 74
            $names = $this->attributes();
554 74
        }
555 77
        $names = array_flip($names);
556 77
        $attributes = [];
557 77
        if ($this->_oldAttributes === null) {
558 61
            foreach ($this->_attributes as $name => $value) {
559 58
                if (isset($names[$name])) {
560 58
                    $attributes[$name] = $value;
561 58
                }
562 61
            }
563 61
        } else {
564 26
            foreach ($this->_attributes as $name => $value) {
565 26
                if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) {
566 25
                    $attributes[$name] = $value;
567 25
                }
568 26
            }
569
        }
570 77
        return $attributes;
571
    }
572
573
    /**
574
     * Saves the current record.
575
     *
576
     * This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]]
577
     * when [[isNewRecord]] is false.
578
     *
579
     * For example, to save a customer record:
580
     *
581
     * ```php
582
     * $customer = new Customer; // or $customer = Customer::findOne($id);
583
     * $customer->name = $name;
584
     * $customer->email = $email;
585
     * $customer->save();
586
     * ```
587
     *
588
     * @param boolean $runValidation whether to perform validation (calling [[validate()]])
589
     * before saving the record. Defaults to `true`. If the validation fails, the record
590
     * will not be saved to the database and this method will return `false`.
591
     * @param array $attributeNames list of attribute names that need to be saved. Defaults to null,
592
     * meaning all attributes that are loaded from DB will be saved.
593
     * @return boolean whether the saving succeeded (i.e. no validation errors occurred).
594
     */
595 74
    public function save($runValidation = true, $attributeNames = null)
596
    {
597 74
        if ($this->getIsNewRecord()) {
598 61
            return $this->insert($runValidation, $attributeNames);
599
        } else {
600 23
            return $this->update($runValidation, $attributeNames) !== false;
601
        }
602
    }
603
604
    /**
605
     * Saves the changes to this active record into the associated database table.
606
     *
607
     * This method performs the following steps in order:
608
     *
609
     * 1. call [[beforeValidate()]] when `$runValidation` is true. If [[beforeValidate()]]
610
     *    returns `false`, the rest of the steps will be skipped;
611
     * 2. call [[afterValidate()]] when `$runValidation` is true. If validation
612
     *    failed, the rest of the steps will be skipped;
613
     * 3. call [[beforeSave()]]. If [[beforeSave()]] returns `false`,
614
     *    the rest of the steps will be skipped;
615
     * 4. save the record into database. If this fails, it will skip the rest of the steps;
616
     * 5. call [[afterSave()]];
617
     *
618
     * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
619
     * [[EVENT_AFTER_VALIDATE]], [[EVENT_BEFORE_UPDATE]], and [[EVENT_AFTER_UPDATE]]
620
     * will be raised by the corresponding methods.
621
     *
622
     * Only the [[dirtyAttributes|changed attribute values]] will be saved into database.
623
     *
624
     * For example, to update a customer record:
625
     *
626
     * ```php
627
     * $customer = Customer::findOne($id);
628
     * $customer->name = $name;
629
     * $customer->email = $email;
630
     * $customer->update();
631
     * ```
632
     *
633
     * Note that it is possible the update does not affect any row in the table.
634
     * In this case, this method will return 0. For this reason, you should use the following
635
     * code to check if update() is successful or not:
636
     *
637
     * ```php
638
     * if ($customer->update() !== false) {
639
     *     // update successful
640
     * } else {
641
     *     // update failed
642
     * }
643
     * ```
644
     *
645
     * @param boolean $runValidation whether to perform validation (calling [[validate()]])
646
     * before saving the record. Defaults to `true`. If the validation fails, the record
647
     * will not be saved to the database and this method will return `false`.
648
     * @param array $attributeNames list of attribute names that need to be saved. Defaults to null,
649
     * meaning all attributes that are loaded from DB will be saved.
650
     * @return integer|boolean the number of rows affected, or false if validation fails
651
     * or [[beforeSave()]] stops the updating process.
652
     * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
653
     * being updated is outdated.
654
     * @throws Exception in case update failed.
655
     */
656
    public function update($runValidation = true, $attributeNames = null)
657
    {
658
        if ($runValidation && !$this->validate($attributeNames)) {
659
            return false;
660
        }
661
        return $this->updateInternal($attributeNames);
662
    }
663
664
    /**
665
     * Updates the specified attributes.
666
     *
667
     * This method is a shortcut to [[update()]] when data validation is not needed
668
     * and only a small set attributes need to be updated.
669
     *
670
     * You may specify the attributes to be updated as name list or name-value pairs.
671
     * If the latter, the corresponding attribute values will be modified accordingly.
672
     * The method will then save the specified attributes into database.
673
     *
674
     * Note that this method will **not** perform data validation and will **not** trigger events.
675
     *
676
     * @param array $attributes the attributes (names or name-value pairs) to be updated
677
     * @return integer the number of rows affected.
678
     */
679 3
    public function updateAttributes($attributes)
680
    {
681 3
        $attrs = [];
682 3
        foreach ($attributes as $name => $value) {
683 3
            if (is_int($name)) {
684 3
                $attrs[] = $value;
685 3
            } else {
686 3
                $this->$name = $value;
687 3
                $attrs[] = $name;
688
            }
689 3
        }
690
691 3
        $values = $this->getDirtyAttributes($attrs);
692 3
        if (empty($values)) {
693
            return 0;
694
        }
695
696 3
        $rows = static::updateAll($values, $this->getOldPrimaryKey(true));
697
698 3
        foreach ($values as $name => $value) {
699 3
            $this->_oldAttributes[$name] = $this->_attributes[$name];
700 3
        }
701
702 3
        return $rows;
703
    }
704
705
    /**
706
     * @see update()
707
     * @param array $attributes attributes to update
708
     * @return integer number of rows updated
709
     * @throws StaleObjectException
710
     */
711 23
    protected function updateInternal($attributes = null)
712
    {
713 23
        if (!$this->beforeSave(false)) {
714
            return false;
715
        }
716 23
        $values = $this->getDirtyAttributes($attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 711 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...
717 23
        if (empty($values)) {
718 2
            $this->afterSave(false, $values);
719 2
            return 0;
720
        }
721 22
        $condition = $this->getOldPrimaryKey(true);
722 22
        $lock = $this->optimisticLock();
723 22
        if ($lock !== null) {
724 3
            $values[$lock] = $this->$lock + 1;
725 3
            $condition[$lock] = $this->$lock;
726 3
        }
727
        // We do not check the return value of updateAll() because it's possible
728
        // that the UPDATE statement doesn't change anything and thus returns 0.
729 22
        $rows = static::updateAll($values, $condition);
730
731 22
        if ($lock !== null && !$rows) {
732 3
            throw new StaleObjectException('The object being updated is outdated.');
733
        }
734
735 22
        if (isset($values[$lock])) {
736 3
            $this->$lock = $values[$lock];
737 3
        }
738
739 22
        $changedAttributes = [];
740 22
        foreach ($values as $name => $value) {
741 22
            $changedAttributes[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
742 22
            $this->_oldAttributes[$name] = $value;
743 22
        }
744 22
        $this->afterSave(false, $changedAttributes);
745
746 22
        return $rows;
747
    }
748
749
    /**
750
     * Updates one or several counter columns for the current AR object.
751
     * Note that this method differs from [[updateAllCounters()]] in that it only
752
     * saves counters for the current AR object.
753
     *
754
     * An example usage is as follows:
755
     *
756
     * ```php
757
     * $post = Post::findOne($id);
758
     * $post->updateCounters(['view_count' => 1]);
759
     * ```
760
     *
761
     * @param array $counters the counters to be updated (attribute name => increment value)
762
     * Use negative values if you want to decrement the counters.
763
     * @return boolean whether the saving is successful
764
     * @see updateAllCounters()
765
     */
766 6
    public function updateCounters($counters)
767
    {
768 6
        if (static::updateAllCounters($counters, $this->getOldPrimaryKey(true)) > 0) {
769 6
            foreach ($counters as $name => $value) {
770 6
                if (!isset($this->_attributes[$name])) {
771 3
                    $this->_attributes[$name] = $value;
772 3
                } else {
773 3
                    $this->_attributes[$name] += $value;
774
                }
775 6
                $this->_oldAttributes[$name] = $this->_attributes[$name];
776 6
            }
777 6
            return true;
778
        } else {
779
            return false;
780
        }
781
    }
782
783
    /**
784
     * Deletes the table row corresponding to this active record.
785
     *
786
     * This method performs the following steps in order:
787
     *
788
     * 1. call [[beforeDelete()]]. If the method returns false, it will skip the
789
     *    rest of the steps;
790
     * 2. delete the record from the database;
791
     * 3. call [[afterDelete()]].
792
     *
793
     * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
794
     * will be raised by the corresponding methods.
795
     *
796
     * @return integer|false the number of rows deleted, or false if the deletion is unsuccessful for some reason.
797
     * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
798
     * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
799
     * being deleted is outdated.
800
     * @throws Exception in case delete failed.
801
     */
802
    public function delete()
803
    {
804
        $result = false;
805
        if ($this->beforeDelete()) {
806
            // we do not check the return value of deleteAll() because it's possible
807
            // the record is already deleted in the database and thus the method will return 0
808
            $condition = $this->getOldPrimaryKey(true);
809
            $lock = $this->optimisticLock();
810
            if ($lock !== null) {
811
                $condition[$lock] = $this->$lock;
812
            }
813
            $result = static::deleteAll($condition);
814
            if ($lock !== null && !$result) {
815
                throw new StaleObjectException('The object being deleted is outdated.');
816
            }
817
            $this->_oldAttributes = null;
818
            $this->afterDelete();
819
        }
820
821
        return $result;
822
    }
823
824
    /**
825
     * Returns a value indicating whether the current record is new.
826
     * @return boolean whether the record is new and should be inserted when calling [[save()]].
827
     */
828 104
    public function getIsNewRecord()
829
    {
830 104
        return $this->_oldAttributes === null;
831
    }
832
833
    /**
834
     * Sets the value indicating whether the record is new.
835
     * @param boolean $value whether the record is new and should be inserted when calling [[save()]].
836
     * @see getIsNewRecord()
837
     */
838
    public function setIsNewRecord($value)
839
    {
840
        $this->_oldAttributes = $value ? null : $this->_attributes;
841
    }
842
843
    /**
844
     * Initializes the object.
845
     * This method is called at the end of the constructor.
846
     * The default implementation will trigger an [[EVENT_INIT]] event.
847
     * If you override this method, make sure you call the parent implementation at the end
848
     * to ensure triggering of the event.
849
     */
850 260
    public function init()
851
    {
852 260
        parent::init();
853 260
        $this->trigger(self::EVENT_INIT);
854 260
    }
855
856
    /**
857
     * This method is called when the AR object is created and populated with the query result.
858
     * The default implementation will trigger an [[EVENT_AFTER_FIND]] event.
859
     * When overriding this method, make sure you call the parent implementation to ensure the
860
     * event is triggered.
861
     */
862 225
    public function afterFind()
863
    {
864 225
        $this->trigger(self::EVENT_AFTER_FIND);
865 225
    }
866
867
    /**
868
     * This method is called at the beginning of inserting or updating a record.
869
     * The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is true,
870
     * or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is false.
871
     * When overriding this method, make sure you call the parent implementation like the following:
872
     *
873
     * ```php
874
     * public function beforeSave($insert)
875
     * {
876
     *     if (parent::beforeSave($insert)) {
877
     *         // ...custom code here...
878
     *         return true;
879
     *     } else {
880
     *         return false;
881
     *     }
882
     * }
883
     * ```
884
     *
885
     * @param boolean $insert whether this method called while inserting a record.
886
     * If false, it means the method is called while updating a record.
887
     * @return boolean whether the insertion or updating should continue.
888
     * If false, the insertion or updating will be cancelled.
889
     */
890 74
    public function beforeSave($insert)
891
    {
892 74
        $event = new ModelEvent;
893 74
        $this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event);
894
895 74
        return $event->isValid;
896
    }
897
898
    /**
899
     * This method is called at the end of inserting or updating a record.
900
     * The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is true,
901
     * or an [[EVENT_AFTER_UPDATE]] event if `$insert` is false. The event class used is [[AfterSaveEvent]].
902
     * When overriding this method, make sure you call the parent implementation so that
903
     * the event is triggered.
904
     * @param boolean $insert whether this method called while inserting a record.
905
     * If false, it means the method is called while updating a record.
906
     * @param array $changedAttributes The old values of attributes that had changed and were saved.
907
     * You can use this parameter to take action based on the changes made for example send an email
908
     * when the password had changed or implement audit trail that tracks all the changes.
909
     * `$changedAttributes` gives you the old attribute values while the active record (`$this`) has
910
     * already the new, updated values.
911
     */
912 74
    public function afterSave($insert, $changedAttributes)
913
    {
914 74
        $this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE, new AfterSaveEvent([
915 74
            'changedAttributes' => $changedAttributes,
916 74
        ]));
917 74
    }
918
919
    /**
920
     * This method is invoked before deleting a record.
921
     * The default implementation raises the [[EVENT_BEFORE_DELETE]] event.
922
     * When overriding this method, make sure you call the parent implementation like the following:
923
     *
924
     * ```php
925
     * public function beforeDelete()
926
     * {
927
     *     if (parent::beforeDelete()) {
928
     *         // ...custom code here...
929
     *         return true;
930
     *     } else {
931
     *         return false;
932
     *     }
933
     * }
934
     * ```
935
     *
936
     * @return boolean whether the record should be deleted. Defaults to true.
937
     */
938 6
    public function beforeDelete()
939
    {
940 6
        $event = new ModelEvent;
941 6
        $this->trigger(self::EVENT_BEFORE_DELETE, $event);
942
943 6
        return $event->isValid;
944
    }
945
946
    /**
947
     * This method is invoked after deleting a record.
948
     * The default implementation raises the [[EVENT_AFTER_DELETE]] event.
949
     * You may override this method to do postprocessing after the record is deleted.
950
     * Make sure you call the parent implementation so that the event is raised properly.
951
     */
952 6
    public function afterDelete()
953
    {
954 6
        $this->trigger(self::EVENT_AFTER_DELETE);
955 6
    }
956
957
    /**
958
     * Repopulates this active record with the latest data.
959
     *
960
     * If the refresh is successful, an [[EVENT_AFTER_REFRESH]] event will be triggered.
961
     * This event is available since version 2.0.8.
962
     *
963
     * @return boolean whether the row still exists in the database. If true, the latest data
964
     * will be populated to this active record. Otherwise, this record will remain unchanged.
965
     */
966 17
    public function refresh()
967
    {
968
        /* @var $record BaseActiveRecord */
969 17
        $record = static::findOne($this->getPrimaryKey(true));
970 17
        if ($record === null) {
971 3
            return false;
972
        }
973 17
        foreach ($this->attributes() as $name) {
974 17
            $this->_attributes[$name] = isset($record->_attributes[$name]) ? $record->_attributes[$name] : null;
975 17
        }
976 17
        $this->_oldAttributes = $this->_attributes;
977 17
        $this->_related = [];
978 17
        $this->afterRefresh();
979
980 17
        return true;
981
    }
982
983
    /**
984
     * This method is called when the AR object is refreshed.
985
     * The default implementation will trigger an [[EVENT_AFTER_REFRESH]] event.
986
     * When overriding this method, make sure you call the parent implementation to ensure the
987
     * event is triggered.
988
     * @since 2.0.8
989
     */
990 17
    public function afterRefresh()
991
    {
992 17
        $this->trigger(self::EVENT_AFTER_REFRESH);
993 17
    }
994
995
    /**
996
     * Returns a value indicating whether the given active record is the same as the current one.
997
     * The comparison is made by comparing the table names and the primary key values of the two active records.
998
     * If one of the records [[isNewRecord|is new]] they are also considered not equal.
999
     * @param ActiveRecordInterface $record record to compare to
1000
     * @return boolean whether the two active records refer to the same row in the same database table.
1001
     */
1002
    public function equals($record)
1003
    {
1004
        if ($this->getIsNewRecord() || $record->getIsNewRecord()) {
1005
            return false;
1006
        }
1007
1008
        return get_class($this) === get_class($record) && $this->getPrimaryKey() === $record->getPrimaryKey();
1009
    }
1010
1011
    /**
1012
     * Returns the primary key value(s).
1013
     * @param boolean $asArray whether to return the primary key value as an array. If true,
1014
     * the return value will be an array with column names as keys and column values as values.
1015
     * Note that for composite primary keys, an array will always be returned regardless of this parameter value.
1016
     * @property mixed The primary key value. An array (column name => column value) is returned if
1017
     * the primary key is composite. A string is returned otherwise (null will be returned if
1018
     * the key value is null).
1019
     * @return mixed the primary key value. An array (column name => column value) is returned if the primary key
1020
     * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
1021
     * the key value is null).
1022
     */
1023 43
    public function getPrimaryKey($asArray = false)
1024
    {
1025 43
        $keys = $this->primaryKey();
1026 43
        if (!$asArray && count($keys) === 1) {
1027 26
            return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null;
1028
        } else {
1029 20
            $values = [];
1030 20
            foreach ($keys as $name) {
1031 20
                $values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
1032 20
            }
1033
1034 20
            return $values;
1035
        }
1036
    }
1037
1038
    /**
1039
     * Returns the old primary key value(s).
1040
     * This refers to the primary key value that is populated into the record
1041
     * after executing a find method (e.g. find(), findOne()).
1042
     * The value remains unchanged even if the primary key attribute is manually assigned with a different value.
1043
     * @param boolean $asArray whether to return the primary key value as an array. If true,
1044
     * the return value will be an array with column name as key and column value as value.
1045
     * If this is false (default), a scalar value will be returned for non-composite primary key.
1046
     * @property mixed The old primary key value. An array (column name => column value) is
1047
     * returned if the primary key is composite. A string is returned otherwise (null will be
1048
     * returned if the key value is null).
1049
     * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key
1050
     * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
1051
     * the key value is null).
1052
     * @throws Exception if the AR model does not have a primary key
1053
     */
1054 43
    public function getOldPrimaryKey($asArray = false)
1055
    {
1056 43
        $keys = $this->primaryKey();
1057 43
        if (empty($keys)) {
1058
            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.');
1059
        }
1060 43
        if (!$asArray && count($keys) === 1) {
1061 7
            return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null;
1062
        } else {
1063 37
            $values = [];
1064 37
            foreach ($keys as $name) {
1065 37
                $values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
1066 37
            }
1067
1068 37
            return $values;
1069
        }
1070
    }
1071
1072
    /**
1073
     * Populates an active record object using a row of data from the database/storage.
1074
     *
1075
     * This is an internal method meant to be called to create active record objects after
1076
     * fetching data from the database. It is mainly used by [[ActiveQuery]] to populate
1077
     * the query results into active records.
1078
     *
1079
     * When calling this method manually you should call [[afterFind()]] on the created
1080
     * record to trigger the [[EVENT_AFTER_FIND|afterFind Event]].
1081
     *
1082
     * @param BaseActiveRecord $record the record to be populated. In most cases this will be an instance
1083
     * created by [[instantiate()]] beforehand.
1084
     * @param array $row attribute values (name => value)
1085
     */
1086 225
    public static function populateRecord($record, $row)
1087
    {
1088 225
        $columns = array_flip($record->attributes());
1089 225
        foreach ($row as $name => $value) {
1090 225
            if (isset($columns[$name])) {
1091 225
                $record->_attributes[$name] = $value;
1092 225
            } elseif ($record->canSetProperty($name)) {
1093 6
                $record->$name = $value;
1094 6
            }
1095 225
        }
1096 225
        $record->_oldAttributes = $record->_attributes;
1097 225
    }
1098
1099
    /**
1100
     * Creates an active record instance.
1101
     *
1102
     * This method is called together with [[populateRecord()]] by [[ActiveQuery]].
1103
     * It is not meant to be used for creating new records directly.
1104
     *
1105
     * You may override this method if the instance being created
1106
     * depends on the row data to be populated into the record.
1107
     * For example, by creating a record based on the value of a column,
1108
     * you may implement the so-called single-table inheritance mapping.
1109
     * @param array $row row data to be populated into the record.
1110
     * @return static the newly created active record
1111
     */
1112 222
    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...
1113
    {
1114 222
        return new static;
1115
    }
1116
1117
    /**
1118
     * Returns whether there is an element at the specified offset.
1119
     * This method is required by the interface [[\ArrayAccess]].
1120
     * @param mixed $offset the offset to check on
1121
     * @return boolean whether there is an element at the specified offset.
1122
     */
1123 24
    public function offsetExists($offset)
1124
    {
1125 24
        return $this->__isset($offset);
1126
    }
1127
1128
    /**
1129
     * Returns the relation object with the specified name.
1130
     * A relation is defined by a getter method which returns an [[ActiveQueryInterface]] object.
1131
     * It can be declared in either the Active Record class itself or one of its behaviors.
1132
     * @param string $name the relation name
1133
     * @param boolean $throwException whether to throw exception if the relation does not exist.
1134
     * @return ActiveQueryInterface|ActiveQuery the relational query object. If the relation does not exist
1135
     * and `$throwException` is false, null will be returned.
1136
     * @throws InvalidParamException if the named relation does not exist.
1137
     */
1138 109
    public function getRelation($name, $throwException = true)
1139
    {
1140 109
        $getter = 'get' . $name;
1141
        try {
1142
            // the relation could be defined in a behavior
1143 109
            $relation = $this->$getter();
1144 109
        } catch (UnknownMethodException $e) {
1145
            if ($throwException) {
1146
                throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
1147
            } else {
1148
                return null;
1149
            }
1150
        }
1151 109
        if (!$relation instanceof ActiveQueryInterface) {
1152
            if ($throwException) {
1153
                throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".');
1154
            } else {
1155
                return null;
1156
            }
1157
        }
1158
1159 109
        if (method_exists($this, $getter)) {
1160
            // relation name is case sensitive, trying to validate it when the relation is defined within this class
1161 109
            $method = new \ReflectionMethod($this, $getter);
1162 109
            $realName = lcfirst(substr($method->getName(), 3));
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
1163 109
            if ($realName !== $name) {
1164
                if ($throwException) {
1165
                    throw new InvalidParamException('Relation names are case sensitive. ' . get_class($this) . " has a relation named \"$realName\" instead of \"$name\".");
1166
                } else {
1167
                    return null;
1168
                }
1169
            }
1170 109
        }
1171
1172 109
        return $relation;
1173
    }
1174
1175
    /**
1176
     * Establishes the relationship between two models.
1177
     *
1178
     * The relationship is established by setting the foreign key value(s) in one model
1179
     * to be the corresponding primary key value(s) in the other model.
1180
     * The model with the foreign key will be saved into database without performing validation.
1181
     *
1182
     * If the relationship involves a junction table, a new row will be inserted into the
1183
     * junction table which contains the primary key values from both models.
1184
     *
1185
     * Note that this method requires that the primary key value is not null.
1186
     *
1187
     * @param string $name the case sensitive name of the relationship.
1188
     * @param ActiveRecordInterface $model the model to be linked with the current one.
1189
     * @param array $extraColumns additional column values to be saved into the junction table.
1190
     * This parameter is only meaningful for a relationship involving a junction table
1191
     * (i.e., a relation set with [[ActiveRelationTrait::via()]] or [[ActiveQuery::viaTable()]].)
1192
     * @throws InvalidCallException if the method is unable to link two models.
1193
     */
1194 9
    public function link($name, $model, $extraColumns = [])
1195
    {
1196 9
        $relation = $this->getRelation($name);
1197
1198 9
        if ($relation->via !== null) {
0 ignored issues
show
Bug introduced by
Accessing via on the interface yii\db\ActiveQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1199 3
            if ($this->getIsNewRecord() || $model->getIsNewRecord()) {
1200
                throw new InvalidCallException('Unable to link models: the models being linked cannot be newly created.');
1201
            }
1202 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...
1203
                /* @var $viaRelation ActiveQuery */
1204 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...
1205 3
                $viaClass = $viaRelation->modelClass;
1206
                // unset $viaName so that it can be reloaded to reflect the change
1207 3
                unset($this->_related[$viaName]);
1208 3
            } else {
1209
                $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...
1210
                $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...
1211
            }
1212 3
            $columns = [];
1213 3
            foreach ($viaRelation->link as $a => $b) {
1214 3
                $columns[$a] = $this->$b;
1215 3
            }
1216 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...
1217 3
                $columns[$b] = $model->$a;
1218 3
            }
1219 3
            foreach ($extraColumns as $k => $v) {
1220 3
                $columns[$k] = $v;
1221 3
            }
1222 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...
1223
                /* @var $viaClass ActiveRecordInterface */
1224
                /* @var $record ActiveRecordInterface */
1225 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...
1226 3
                foreach ($columns as $column => $value) {
1227 3
                    $record->$column = $value;
1228 3
                }
1229 3
                $record->insert(false);
1230 3
            } else {
1231
                /* @var $viaTable string */
1232
                static::getDb()->createCommand()
1233
                    ->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...
1234
            }
1235 3
        } else {
1236 9
            $p1 = $model->isPrimaryKey(array_keys($relation->link));
0 ignored issues
show
Bug introduced by
Accessing link on the interface yii\db\ActiveQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1237 9
            $p2 = static::isPrimaryKey(array_values($relation->link));
0 ignored issues
show
Bug introduced by
Accessing link on the interface yii\db\ActiveQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1238 9
            if ($p1 && $p2) {
1239
                if ($this->getIsNewRecord() && $model->getIsNewRecord()) {
1240
                    throw new InvalidCallException('Unable to link models: at most one model can be newly created.');
1241
                } elseif ($this->getIsNewRecord()) {
1242
                    $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...
1243
                } else {
1244
                    $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...
1245
                }
1246 9
            } elseif ($p1) {
1247 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...
1248 9
            } elseif ($p2) {
1249 9
                $this->bindModels($relation->link, $model, $this);
0 ignored issues
show
Bug introduced by
Accessing link on the interface yii\db\ActiveQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1250 9
            } else {
1251
                throw new InvalidCallException('Unable to link models: the link defining the relation does not involve any primary key.');
1252
            }
1253
        }
1254
1255
        // update lazily loaded related objects
1256 9
        if (!$relation->multiple) {
0 ignored issues
show
Bug introduced by
Accessing multiple on the interface yii\db\ActiveQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1257 3
            $this->_related[$name] = $model;
1258 9
        } elseif (isset($this->_related[$name])) {
1259 9
            if ($relation->indexBy !== null) {
0 ignored issues
show
Bug introduced by
Accessing indexBy on the interface yii\db\ActiveQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1260 6
                if ($relation->indexBy instanceof \Closure) {
0 ignored issues
show
Bug introduced by
Accessing indexBy on the interface yii\db\ActiveQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1261 3
                    $index = call_user_func($relation->indexBy, $model);
0 ignored issues
show
Bug introduced by
Accessing indexBy on the interface yii\db\ActiveQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1262 3
                } else {
1263 3
                    $index = $model->{$relation->indexBy};
0 ignored issues
show
Bug introduced by
Accessing indexBy on the interface yii\db\ActiveQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1264
                }
1265 6
                $this->_related[$name][$index] = $model;
1266 6
            } else {
1267 3
                $this->_related[$name][] = $model;
1268
            }
1269 9
        }
1270 9
    }
1271
1272
    /**
1273
     * Destroys the relationship between two models.
1274
     *
1275
     * The model with the foreign key of the relationship will be deleted if `$delete` is true.
1276
     * Otherwise, the foreign key will be set null and the model will be saved without validation.
1277
     *
1278
     * @param string $name the case sensitive name of the relationship.
1279
     * @param ActiveRecordInterface $model the model to be unlinked from the current one.
1280
     * You have to make sure that the model is really related with the current model as this method
1281
     * does not check this.
1282
     * @param boolean $delete whether to delete the model that contains the foreign key.
1283
     * If false, the model's foreign key will be set null and saved.
1284
     * If true, the model containing the foreign key will be deleted.
1285
     * @throws InvalidCallException if the models cannot be unlinked
1286
     */
1287 3
    public function unlink($name, $model, $delete = false)
1288
    {
1289 3
        $relation = $this->getRelation($name);
1290
1291 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...
1292 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...
1293
                /* @var $viaRelation ActiveQuery */
1294 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...
1295 3
                $viaClass = $viaRelation->modelClass;
1296 3
                unset($this->_related[$viaName]);
1297 3
            } else {
1298 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...
1299 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...
1300
            }
1301 3
            $columns = [];
1302 3
            foreach ($viaRelation->link as $a => $b) {
1303 3
                $columns[$a] = $this->$b;
1304 3
            }
1305 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...
1306 3
                $columns[$b] = $model->$a;
1307 3
            }
1308 3
            $nulls = [];
1309 3
            foreach (array_keys($columns) as $a) {
1310 3
                $nulls[$a] = null;
1311 3
            }
1312 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...
1313
                /* @var $viaClass ActiveRecordInterface */
1314 3
                if ($delete) {
1315 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...
1316 3
                } else {
1317
                    $viaClass::updateAll($nulls, $columns);
1318
                }
1319 3
            } else {
1320
                /* @var $viaTable string */
1321
                /* @var $command Command */
1322 3
                $command = static::getDb()->createCommand();
1323 3
                if ($delete) {
1324
                    $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...
1325
                } else {
1326 3
                    $command->update($viaTable, $nulls, $columns)->execute();
1327
                }
1328
            }
1329 3
        } else {
1330 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...
1331 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...
1332 3
            if ($p2) {
1333 3
                if ($delete) {
1334 3
                    $model->delete();
1335 3
                } else {
1336 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...
1337 3
                        $model->$a = null;
1338 3
                    }
1339 3
                    $model->save(false);
1340
                }
1341 3
            } elseif ($p1) {
1342
                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...
1343
                    if (is_array($this->$b)) { // relation via array valued attribute
1344
                        if (($key = array_search($model->$a, $this->$b, false)) !== false) {
1345
                            $values = $this->$b;
1346
                            unset($values[$key]);
1347
                            $this->$b = array_values($values);
1348
                        }
1349
                    } else {
1350
                        $this->$b = null;
1351
                    }
1352
                }
1353
                $delete ? $this->delete() : $this->save(false);
1354
            } else {
1355
                throw new InvalidCallException('Unable to unlink models: the link does not involve any primary key.');
1356
            }
1357
        }
1358
1359 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...
1360
            unset($this->_related[$name]);
1361 3
        } elseif (isset($this->_related[$name])) {
1362
            /* @var $b ActiveRecordInterface */
1363 3
            foreach ($this->_related[$name] as $a => $b) {
1364 3
                if ($model->getPrimaryKey() === $b->getPrimaryKey()) {
1365 3
                    unset($this->_related[$name][$a]);
1366 3
                }
1367 3
            }
1368 3
        }
1369 3
    }
1370
1371
    /**
1372
     * Destroys the relationship in current model.
1373
     *
1374
     * The model with the foreign key of the relationship will be deleted if `$delete` is true.
1375
     * Otherwise, the foreign key will be set null and the model will be saved without validation.
1376
     *
1377
     * Note that to destroy the relationship without removing records make sure your keys can be set to null
1378
     *
1379
     * @param string $name the case sensitive name of the relationship.
1380
     * @param boolean $delete whether to delete the model that contains the foreign key.
1381
     */
1382 12
    public function unlinkAll($name, $delete = false)
1383
    {
1384 12
        $relation = $this->getRelation($name);
1385
1386 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...
1387 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...
1388
                /* @var $viaRelation ActiveQuery */
1389 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...
1390 3
                $viaClass = $viaRelation->modelClass;
1391 3
                unset($this->_related[$viaName]);
1392 3
            } else {
1393 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...
1394 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...
1395
            }
1396 6
            $condition = [];
1397 6
            $nulls = [];
1398 6
            foreach ($viaRelation->link as $a => $b) {
1399 6
                $nulls[$a] = null;
1400 6
                $condition[$a] = $this->$b;
1401 6
            }
1402 6
            if (!empty($viaRelation->where)) {
1403
                $condition = ['and', $condition, $viaRelation->where];
1404
            }
1405 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...
1406
                /* @var $viaClass ActiveRecordInterface */
1407 3
                if ($delete) {
1408 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...
1409 3
                } else {
1410 3
                    $viaClass::updateAll($nulls, $condition);
1411
                }
1412 3
            } else {
1413
                /* @var $viaTable string */
1414
                /* @var $command Command */
1415 3
                $command = static::getDb()->createCommand();
1416 3
                if ($delete) {
1417 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...
1418 3
                } else {
1419 3
                    $command->update($viaTable, $nulls, $condition)->execute();
1420
                }
1421
            }
1422 6
        } else {
1423
            /* @var $relatedModel ActiveRecordInterface */
1424 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...
1425 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...
1426
                // relation via array valued attribute
1427
                $this->$b = [];
1428
                $this->save(false);
1429
            } else {
1430 9
                $nulls = [];
1431 9
                $condition = [];
1432 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...
1433 9
                    $nulls[$a] = null;
1434 9
                    $condition[$a] = $this->$b;
1435 9
                }
1436 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...
1437 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...
1438 6
                }
1439 9
                if ($delete) {
1440 6
                    $relatedModel::deleteAll($condition);
1441 6
                } else {
1442 6
                    $relatedModel::updateAll($nulls, $condition);
1443
                }
1444
            }
1445
        }
1446
1447 12
        unset($this->_related[$name]);
1448 12
    }
1449
1450
    /**
1451
     * @param array $link
1452
     * @param ActiveRecordInterface $foreignModel
1453
     * @param ActiveRecordInterface $primaryModel
1454
     * @throws InvalidCallException
1455
     */
1456 9
    private function bindModels($link, $foreignModel, $primaryModel)
1457
    {
1458 9
        foreach ($link as $fk => $pk) {
1459 9
            $value = $primaryModel->$pk;
1460 9
            if ($value === null) {
1461
                throw new InvalidCallException('Unable to link models: the primary key of ' . get_class($primaryModel) . ' is null.');
1462
            }
1463 9
            if (is_array($foreignModel->$fk)) { // relation via array valued attribute
1464
                $foreignModel->$fk = array_merge($foreignModel->$fk, [$value]);
1465
            } else {
1466 9
                $foreignModel->$fk = $value;
1467
            }
1468 9
        }
1469 9
        $foreignModel->save(false);
1470 9
    }
1471
1472
    /**
1473
     * Returns a value indicating whether the given set of attributes represents the primary key for this model
1474
     * @param array $keys the set of attributes to check
1475
     * @return boolean whether the given set of attributes represents the primary key for this model
1476
     */
1477 15
    public static function isPrimaryKey($keys)
1478
    {
1479 15
        $pks = static::primaryKey();
1480 15
        if (count($keys) === count($pks)) {
1481 15
            return count(array_intersect($keys, $pks)) === count($pks);
1482
        } else {
1483 9
            return false;
1484
        }
1485
    }
1486
1487
    /**
1488
     * Returns the text label for the specified attribute.
1489
     * If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model.
1490
     * @param string $attribute the attribute name
1491
     * @return string the attribute label
1492
     * @see generateAttributeLabel()
1493
     * @see attributeLabels()
1494
     */
1495 42
    public function getAttributeLabel($attribute)
1496
    {
1497 42
        $labels = $this->attributeLabels();
1498 42
        if (isset($labels[$attribute])) {
1499 10
            return $labels[$attribute];
1500 39
        } elseif (strpos($attribute, '.')) {
1501
            $attributeParts = explode('.', $attribute);
1502
            $neededAttribute = array_pop($attributeParts);
1503
1504
            $relatedModel = $this;
1505
            foreach ($attributeParts as $relationName) {
1506
                if ($relatedModel->isRelationPopulated($relationName) && $relatedModel->$relationName instanceof self) {
1507
                    $relatedModel = $relatedModel->$relationName;
1508
                } else {
1509
                    try {
1510
                        $relation = $relatedModel->getRelation($relationName);
1511
                    } catch (InvalidParamException $e) {
1512
                        return $this->generateAttributeLabel($attribute);
1513
                    }
1514
                    $relatedModel = new $relation->modelClass;
1515
                }
1516
            }
1517
1518
            $labels = $relatedModel->attributeLabels();
1519
            if (isset($labels[$neededAttribute])) {
1520
                return $labels[$neededAttribute];
1521
            }
1522
        }
1523
1524 39
        return $this->generateAttributeLabel($attribute);
1525
    }
1526
1527
    /**
1528
     * Returns the text hint for the specified attribute.
1529
     * If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model.
1530
     * @param string $attribute the attribute name
1531
     * @return string the attribute hint
1532
     * @see attributeHints()
1533
     * @since 2.0.4
1534
     */
1535
    public function getAttributeHint($attribute)
1536
    {
1537
        $hints = $this->attributeHints();
1538
        if (isset($hints[$attribute])) {
1539
            return $hints[$attribute];
1540
        } elseif (strpos($attribute, '.')) {
1541
            $attributeParts = explode('.', $attribute);
1542
            $neededAttribute = array_pop($attributeParts);
1543
1544
            $relatedModel = $this;
1545
            foreach ($attributeParts as $relationName) {
1546
                if ($relatedModel->isRelationPopulated($relationName) && $relatedModel->$relationName instanceof self) {
1547
                    $relatedModel = $relatedModel->$relationName;
1548
                } else {
1549
                    try {
1550
                        $relation = $relatedModel->getRelation($relationName);
1551
                    } catch (InvalidParamException $e) {
1552
                        return '';
1553
                    }
1554
                    $relatedModel = new $relation->modelClass;
1555
                }
1556
            }
1557
1558
            $hints = $relatedModel->attributeHints();
1559
            if (isset($hints[$neededAttribute])) {
1560
                return $hints[$neededAttribute];
1561
            }
1562
        }
1563
        return '';
1564
    }
1565
1566
    /**
1567
     * @inheritdoc
1568
     *
1569
     * The default implementation returns the names of the columns whose values have been populated into this record.
1570
     */
1571
    public function fields()
1572
    {
1573
        $fields = array_keys($this->_attributes);
1574
1575
        return array_combine($fields, $fields);
1576
    }
1577
1578
    /**
1579
     * @inheritdoc
1580
     *
1581
     * The default implementation returns the names of the relations that have been populated into this record.
1582
     */
1583
    public function extraFields()
1584
    {
1585
        $fields = array_keys($this->getRelatedRecords());
1586
1587
        return array_combine($fields, $fields);
1588
    }
1589
1590
    /**
1591
     * Sets the element value at the specified offset to null.
1592
     * This method is required by the SPL interface [[\ArrayAccess]].
1593
     * It is implicitly called when you use something like `unset($model[$offset])`.
1594
     * @param mixed $offset the offset to unset element
1595
     */
1596 3
    public function offsetUnset($offset)
1597
    {
1598 3
        if (property_exists($this, $offset)) {
1599
            $this->$offset = null;
1600
        } else {
1601 3
            unset($this->$offset);
1602
        }
1603 3
    }
1604
}
1605