ActiveRecord::deleteAll()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db;
9
10
use Yii;
11
use yii\base\InvalidArgumentException;
12
use yii\base\InvalidConfigException;
13
use yii\helpers\ArrayHelper;
14
use yii\helpers\Inflector;
15
use yii\helpers\StringHelper;
16
17
/**
18
 * ActiveRecord is the base class for classes representing relational data in terms of objects.
19
 *
20
 * Active Record implements the [Active Record design pattern](https://en.wikipedia.org/wiki/Active_record_pattern).
21
 * The premise behind Active Record is that an individual [[ActiveRecord]] object is associated with a specific
22
 * row in a database table. The object's attributes are mapped to the columns of the corresponding table.
23
 * Referencing an Active Record attribute is equivalent to accessing the corresponding table column for that record.
24
 *
25
 * As an example, say that the `Customer` ActiveRecord class is associated with the `customer` table.
26
 * This would mean that the class's `name` attribute is automatically mapped to the `name` column in `customer` table.
27
 * Thanks to Active Record, assuming the variable `$customer` is an object of type `Customer`, to get the value of
28
 * the `name` column for the table row, you can use the expression `$customer->name`.
29
 * In this example, Active Record is providing an object-oriented interface for accessing data stored in the database.
30
 * But Active Record provides much more functionality than this.
31
 *
32
 * To declare an ActiveRecord class you need to extend [[\yii\db\ActiveRecord]] and
33
 * implement the `tableName` method:
34
 *
35
 * ```php
36
 * <?php
37
 *
38
 * class Customer extends \yii\db\ActiveRecord
39
 * {
40
 *     public static function tableName()
41
 *     {
42
 *         return 'customer';
43
 *     }
44
 * }
45
 * ```
46
 *
47
 * The `tableName` method only has to return the name of the database table associated with the class.
48
 *
49
 * > Tip: You may also use the [Gii code generator](guide:start-gii) to generate ActiveRecord classes from your
50
 * > database tables.
51
 *
52
 * Class instances are obtained in one of two ways:
53
 *
54
 * * Using the `new` operator to create a new, empty object
55
 * * Using a method to fetch an existing record (or records) from the database
56
 *
57
 * Below is an example showing some typical usage of ActiveRecord:
58
 *
59
 * ```php
60
 * $user = new User();
61
 * $user->name = 'Qiang';
62
 * $user->save();  // a new row is inserted into user table
63
 *
64
 * // the following will retrieve the user 'CeBe' from the database
65
 * $user = User::find()->where(['name' => 'CeBe'])->one();
66
 *
67
 * // this will get related records from orders table when relation is defined
68
 * $orders = $user->orders;
69
 * ```
70
 *
71
 * For more details and usage information on ActiveRecord, see the [guide article on ActiveRecord](guide:db-active-record).
72
 *
73
 * @method ActiveQuery hasMany($class, array $link) See [[BaseActiveRecord::hasMany()]] for more info.
74
 * @method ActiveQuery hasOne($class, array $link) See [[BaseActiveRecord::hasOne()]] for more info.
75
 *
76
 * @author Qiang Xue <[email protected]>
77
 * @author Carsten Brandt <[email protected]>
78
 * @since 2.0
79
 */
80
class ActiveRecord extends BaseActiveRecord
81
{
82
    /**
83
     * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
84
     */
85
    const OP_INSERT = 0x01;
86
    /**
87
     * The update operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
88
     */
89
    const OP_UPDATE = 0x02;
90
    /**
91
     * The delete operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
92
     */
93
    const OP_DELETE = 0x04;
94
    /**
95
     * All three operations: insert, update, delete.
96
     * This is a shortcut of the expression: OP_INSERT | OP_UPDATE | OP_DELETE.
97
     */
98
    const OP_ALL = 0x07;
99
100
101
    /**
102
     * Loads default values from database table schema.
103
     *
104
     * You may call this method to load default values after creating a new instance:
105
     *
106
     * ```php
107
     * // class Customer extends \yii\db\ActiveRecord
108
     * $customer = new Customer();
109
     * $customer->loadDefaultValues();
110
     * ```
111
     *
112
     * @param bool $skipIfSet whether existing value should be preserved.
113
     * This will only set defaults for attributes that are `null`.
114
     * @return $this the model instance itself.
115
     */
116 4
    public function loadDefaultValues($skipIfSet = true)
117
    {
118 4
        $columns = static::getTableSchema()->columns;
119 4
        foreach ($this->attributes() as $name) {
120 4
            if (isset($columns[$name])) {
121 4
                $defaultValue = $columns[$name]->defaultValue;
122 4
                if ($defaultValue !== null && (!$skipIfSet || $this->getAttribute($name) === null)) {
123 4
                    $this->setAttribute($name, $defaultValue);
124
                }
125
            }
126
        }
127
128 4
        return $this;
129
    }
130
131
    /**
132
     * Returns the database connection used by this AR class.
133
     * By default, the "db" application component is used as the database connection.
134
     * You may override this method if you want to use a different database connection.
135
     * @return Connection the database connection used by this AR class.
136
     */
137 55
    public static function getDb()
138
    {
139 55
        return Yii::$app->getDb();
140
    }
141
142
    /**
143
     * Creates an [[ActiveQuery]] instance with a given SQL statement.
144
     *
145
     * Note that because the SQL statement is already specified, calling additional
146
     * query modification methods (such as `where()`, `order()`) on the created [[ActiveQuery]]
147
     * instance will have no effect. However, calling `with()`, `asArray()` or `indexBy()` is
148
     * still fine.
149
     *
150
     * Below is an example:
151
     *
152
     * ```php
153
     * $customers = Customer::findBySql('SELECT * FROM customer')->all();
154
     * ```
155
     *
156
     * @param string $sql the SQL statement to be executed
157
     * @param array $params parameters to be bound to the SQL statement during execution.
158
     * @return ActiveQuery the newly created [[ActiveQuery]] instance
159
     */
160 6
    public static function findBySql($sql, $params = [])
161
    {
162 6
        $query = static::find();
163 6
        $query->sql = $sql;
164
165 6
        return $query->params($params);
166
    }
167
168
    /**
169
     * Finds ActiveRecord instance(s) by the given condition.
170
     * This method is internally called by [[findOne()]] and [[findAll()]].
171
     * @param mixed $condition please refer to [[findOne()]] for the explanation of this parameter
172
     * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance.
173
     * @throws InvalidConfigException if there is no primary key defined.
174
     * @internal
175
     */
176 262
    protected static function findByCondition($condition)
177
    {
178 262
        $query = static::find();
179
180 262
        if (!ArrayHelper::isAssociative($condition) && !$condition instanceof ExpressionInterface) {
181
            // query by primary key
182 173
            $primaryKey = static::primaryKey();
183 173
            if (isset($primaryKey[0])) {
184 173
                $pk = $primaryKey[0];
185 173
                if (!empty($query->join) || !empty($query->joinWith)) {
186 3
                    $pk = static::tableName() . '.' . $pk;
187
                }
188
                // if condition is scalar, search for a single primary key, if it is array, search for multiple primary key values
189 173
                $condition = [$pk => is_array($condition) ? array_values($condition) : $condition];
190
            } else {
191 173
                throw new InvalidConfigException('"' . get_called_class() . '" must have a primary key.');
192
            }
193 107
        } elseif (is_array($condition)) {
194 107
            $aliases = static::filterValidAliases($query);
195 107
            $condition = static::filterCondition($condition, $aliases);
196
        }
197
198 238
        return $query->andWhere($condition);
199
    }
200
201
    /**
202
     * Returns table aliases which are not the same as the name of the tables.
203
     *
204
     * @param Query $query
205
     * @return array
206
     * @throws InvalidConfigException
207
     * @since 2.0.17
208
     * @internal
209
     */
210 125
    protected static function filterValidAliases(Query $query)
211
    {
212 125
        $tables = $query->getTablesUsedInFrom();
213
214 125
        $aliases = array_diff(array_keys($tables), $tables);
215
216 125
        return array_map(function ($alias) {
217 42
            return preg_replace('/{{(\w+)}}/', '$1', $alias);
218 125
        }, array_values($aliases));
219
    }
220
221
    /**
222
     * Filters array condition before it is assiged to a Query filter.
223
     *
224
     * This method will ensure that an array condition only filters on existing table columns.
225
     *
226
     * @param array $condition condition to filter.
227
     * @param array $aliases
228
     * @return array filtered condition.
229
     * @throws InvalidArgumentException in case array contains unsafe values.
230
     * @throws InvalidConfigException
231
     * @since 2.0.15
232
     * @internal
233
     */
234 107
    protected static function filterCondition(array $condition, array $aliases = [])
235
    {
236 107
        $result = [];
237 107
        $db = static::getDb();
238 107
        $columnNames = static::filterValidColumnNames($db, $aliases);
239
240 107
        foreach ($condition as $key => $value) {
241 107
            if (is_string($key) && !in_array($db->quoteSql($key), $columnNames, true)) {
242 24
                throw new InvalidArgumentException('Key "' . $key . '" is not a column name and can not be used as a filter');
243
            }
244 83
            $result[$key] = is_array($value) ? array_values($value) : $value;
245
        }
246
247 83
        return $result;
248
    }
249
250
    /**
251
     * Valid column names are table column names or column names prefixed with table name or table alias
252
     *
253
     * @param Connection $db
254
     * @param array $aliases
255
     * @return array
256
     * @throws InvalidConfigException
257
     * @since 2.0.17
258
     * @internal
259
     */
260 107
    protected static function filterValidColumnNames($db, array $aliases)
261
    {
262 107
        $columnNames = [];
263 107
        $tableName = static::tableName();
264 107
        $quotedTableName = $db->quoteTableName($tableName);
265
266 107
        foreach (static::getTableSchema()->getColumnNames() as $columnName) {
267 107
            $columnNames[] = $columnName;
268 107
            $columnNames[] = $db->quoteColumnName($columnName);
269 107
            $columnNames[] = "$tableName.$columnName";
270 107
            $columnNames[] = $db->quoteSql("$quotedTableName.[[$columnName]]");
271 107
            foreach ($aliases as $tableAlias) {
272 33
                $columnNames[] = "$tableAlias.$columnName";
273 33
                $quotedTableAlias = $db->quoteTableName($tableAlias);
274 33
                $columnNames[] = $db->quoteSql("$quotedTableAlias.[[$columnName]]");
275
            }
276
        }
277
278 107
        return $columnNames;
279
    }
280
281
    /**
282
     * {@inheritdoc}
283
     */
284 29
    public function refresh()
285
    {
286 29
        $query = static::find();
287 29
        $tableName = key($query->getTablesUsedInFrom());
288 29
        $pk = [];
289
        // disambiguate column names in case ActiveQuery adds a JOIN
290 29
        foreach ($this->getPrimaryKey(true) as $key => $value) {
291 29
            $pk[$tableName . '.' . $key] = $value;
292
        }
293 29
        $query->where($pk);
294
295
        /* @var $record BaseActiveRecord */
296 29
        $record = $query->noCache()->one();
297 29
        return $this->refreshInternal($record);
298
    }
299
300
    /**
301
     * Updates the whole table using the provided attribute values and conditions.
302
     *
303
     * For example, to change the status to be 1 for all customers whose status is 2:
304
     *
305
     * ```php
306
     * Customer::updateAll(['status' => 1], 'status = 2');
307
     * ```
308
     *
309
     * > Warning: If you do not specify any condition, this method will update **all** rows in the table.
310
     *
311
     * Note that this method will not trigger any events. If you need [[EVENT_BEFORE_UPDATE]] or
312
     * [[EVENT_AFTER_UPDATE]] to be triggered, you need to [[find()|find]] the models first and then
313
     * call [[update()]] on each of them. For example an equivalent of the example above would be:
314
     *
315
     * ```php
316
     * $models = Customer::find()->where('status = 2')->all();
317
     * foreach ($models as $model) {
318
     *     $model->status = 1;
319
     *     $model->update(false); // skipping validation as no user input is involved
320
     * }
321
     * ```
322
     *
323
     * For a large set of models you might consider using [[ActiveQuery::each()]] to keep memory usage within limits.
324
     *
325
     * @param array $attributes attribute values (name-value pairs) to be saved into the table
326
     * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
327
     * Please refer to [[Query::where()]] on how to specify this parameter.
328
     * @param array $params the parameters (name => value) to be bound to the query.
329
     * @return int the number of rows updated
330
     */
331 56
    public static function updateAll($attributes, $condition = '', $params = [])
332
    {
333 56
        $command = static::getDb()->createCommand();
334 56
        $command->update(static::tableName(), $attributes, $condition, $params);
335
336 56
        return $command->execute();
337
    }
338
339
    /**
340
     * Updates the whole table using the provided counter changes and conditions.
341
     *
342
     * For example, to increment all customers' age by 1,
343
     *
344
     * ```php
345
     * Customer::updateAllCounters(['age' => 1]);
346
     * ```
347
     *
348
     * Note that this method will not trigger any events.
349
     *
350
     * @param array $counters the counters to be updated (attribute name => increment value).
351
     * Use negative values if you want to decrement the counters.
352
     * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
353
     * Please refer to [[Query::where()]] on how to specify this parameter.
354
     * @param array $params the parameters (name => value) to be bound to the query.
355
     * Do not name the parameters as `:bp0`, `:bp1`, etc., because they are used internally by this method.
356
     * @return int the number of rows updated
357
     */
358 6
    public static function updateAllCounters($counters, $condition = '', $params = [])
359
    {
360 6
        $n = 0;
361 6
        foreach ($counters as $name => $value) {
362 6
            $counters[$name] = new Expression("[[$name]]+:bp{$n}", [":bp{$n}" => $value]);
363 6
            $n++;
364
        }
365 6
        $command = static::getDb()->createCommand();
366 6
        $command->update(static::tableName(), $counters, $condition, $params);
367
368 6
        return $command->execute();
369
    }
370
371
    /**
372
     * Deletes rows in the table using the provided conditions.
373
     *
374
     * For example, to delete all customers whose status is 3:
375
     *
376
     * ```php
377
     * Customer::deleteAll('status = 3');
378
     * ```
379
     *
380
     * > Warning: If you do not specify any condition, this method will delete **all** rows in the table.
381
     *
382
     * Note that this method will not trigger any events. If you need [[EVENT_BEFORE_DELETE]] or
383
     * [[EVENT_AFTER_DELETE]] to be triggered, you need to [[find()|find]] the models first and then
384
     * call [[delete()]] on each of them. For example an equivalent of the example above would be:
385
     *
386
     * ```php
387
     * $models = Customer::find()->where('status = 3')->all();
388
     * foreach ($models as $model) {
389
     *     $model->delete();
390
     * }
391
     * ```
392
     *
393
     * For a large set of models you might consider using [[ActiveQuery::each()]] to keep memory usage within limits.
394
     *
395
     * @param string|array|null $condition the conditions that will be put in the WHERE part of the DELETE SQL.
396
     * Please refer to [[Query::where()]] on how to specify this parameter.
397
     * @param array $params the parameters (name => value) to be bound to the query.
398
     * @return int the number of rows deleted
399
     */
400 35
    public static function deleteAll($condition = null, $params = [])
401
    {
402 35
        $command = static::getDb()->createCommand();
403 35
        $command->delete(static::tableName(), $condition, $params);
404
405 35
        return $command->execute();
406
    }
407
408
    /**
409
     * {@inheritdoc}
410
     * @return ActiveQuery the newly created [[ActiveQuery]] instance.
411
     */
412 383
    public static function find()
413
    {
414 383
        return Yii::createObject(ActiveQuery::className(), [get_called_class()]);
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

414
        return Yii::createObject(/** @scrutinizer ignore-deprecated */ ActiveQuery::className(), [get_called_class()]);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
415
    }
416
417
    /**
418
     * Declares the name of the database table associated with this AR class.
419
     * By default this method returns the class name as the table name by calling [[Inflector::camel2id()]]
420
     * with prefix [[Connection::tablePrefix]]. For example if [[Connection::tablePrefix]] is `tbl_`,
421
     * `Customer` becomes `tbl_customer`, and `OrderItem` becomes `tbl_order_item`. You may override this method
422
     * if the table is not named after this convention.
423
     * @return string the table name
424
     */
425 16
    public static function tableName()
426
    {
427 16
        return '{{%' . Inflector::camel2id(StringHelper::basename(get_called_class()), '_') . '}}';
428
    }
429
430
    /**
431
     * Returns the schema information of the DB table associated with this AR class.
432
     * @return TableSchema the schema information of the DB table associated with this AR class.
433
     * @throws InvalidConfigException if the table for the AR class does not exist.
434
     */
435 559
    public static function getTableSchema()
436
    {
437 559
        $tableSchema = static::getDb()
438 559
            ->getSchema()
439 559
            ->getTableSchema(static::tableName());
440
441 559
        if ($tableSchema === null) {
442
            throw new InvalidConfigException('The table does not exist: ' . static::tableName());
443
        }
444
445 559
        return $tableSchema;
446
    }
447
448
    /**
449
     * Returns the primary key name(s) for this AR class.
450
     * The default implementation will return the primary key(s) as declared
451
     * in the DB table that is associated with this AR class.
452
     *
453
     * If the DB table does not declare any primary key, you should override
454
     * this method to return the attributes that you want to use as primary keys
455
     * for this AR class.
456
     *
457
     * Note that an array should be returned even for a table with single primary key.
458
     *
459
     * @return string[] the primary keys of the associated database table.
460
     */
461 304
    public static function primaryKey()
462
    {
463 304
        return static::getTableSchema()->primaryKey;
464
    }
465
466
    /**
467
     * Returns the list of all attribute names of the model.
468
     * The default implementation will return all column names of the table associated with this AR class.
469
     * @return array list of attribute names.
470
     */
471 496
    public function attributes()
472
    {
473 496
        return static::getTableSchema()->getColumnNames();
474
    }
475
476
    /**
477
     * Declares which DB operations should be performed within a transaction in different scenarios.
478
     * The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]],
479
     * which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively.
480
     * By default, these methods are NOT enclosed in a DB transaction.
481
     *
482
     * In some scenarios, to ensure data consistency, you may want to enclose some or all of them
483
     * in transactions. You can do so by overriding this method and returning the operations
484
     * that need to be transactional. For example,
485
     *
486
     * ```php
487
     * return [
488
     *     'admin' => self::OP_INSERT,
489
     *     'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
490
     *     // the above is equivalent to the following:
491
     *     // 'api' => self::OP_ALL,
492
     *
493
     * ];
494
     * ```
495
     *
496
     * The above declaration specifies that in the "admin" scenario, the insert operation ([[insert()]])
497
     * should be done in a transaction; and in the "api" scenario, all the operations should be done
498
     * in a transaction.
499
     *
500
     * @return array the declarations of transactional operations. The array keys are scenarios names,
501
     * and the array values are the corresponding transaction operations.
502
     */
503 128
    public function transactions()
504
    {
505 128
        return [];
506
    }
507
508
    /**
509
     * {@inheritdoc}
510
     */
511 396
    public static function populateRecord($record, $row)
512
    {
513 396
        $columns = static::getTableSchema()->columns;
514 396
        foreach ($row as $name => $value) {
515 396
            if (isset($columns[$name])) {
516 396
                $row[$name] = $columns[$name]->phpTypecast($value);
517
            }
518
        }
519 396
        parent::populateRecord($record, $row);
520
    }
521
522
    /**
523
     * Inserts a row into the associated database table using the attribute values of this record.
524
     *
525
     * This method performs the following steps in order:
526
     *
527
     * 1. call [[beforeValidate()]] when `$runValidation` is `true`. If [[beforeValidate()]]
528
     *    returns `false`, the rest of the steps will be skipped;
529
     * 2. call [[afterValidate()]] when `$runValidation` is `true`. If validation
530
     *    failed, the rest of the steps will be skipped;
531
     * 3. call [[beforeSave()]]. If [[beforeSave()]] returns `false`,
532
     *    the rest of the steps will be skipped;
533
     * 4. insert the record into database. If this fails, it will skip the rest of the steps;
534
     * 5. call [[afterSave()]];
535
     *
536
     * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
537
     * [[EVENT_AFTER_VALIDATE]], [[EVENT_BEFORE_INSERT]], and [[EVENT_AFTER_INSERT]]
538
     * will be raised by the corresponding methods.
539
     *
540
     * Only the [[dirtyAttributes|changed attribute values]] will be inserted into database.
541
     *
542
     * If the table's primary key is auto-incremental and is `null` during insertion,
543
     * it will be populated with the actual value after insertion.
544
     *
545
     * For example, to insert a customer record:
546
     *
547
     * ```php
548
     * $customer = new Customer;
549
     * $customer->name = $name;
550
     * $customer->email = $email;
551
     * $customer->insert();
552
     * ```
553
     *
554
     * @param bool $runValidation whether to perform validation (calling [[validate()]])
555
     * before saving the record. Defaults to `true`. If the validation fails, the record
556
     * will not be saved to the database and this method will return `false`.
557
     * @param array|null $attributes list of attributes that need to be saved. Defaults to `null`,
558
     * meaning all attributes that are loaded from DB will be saved.
559
     * @return bool whether the attributes are valid and the record is inserted successfully.
560
     * @throws \Throwable in case insert failed.
561
     */
562 111
    public function insert($runValidation = true, $attributes = null)
563
    {
564 111
        if ($runValidation && !$this->validate($attributes)) {
565
            Yii::info('Model not inserted due to validation error.', __METHOD__);
566
            return false;
567
        }
568
569 111
        if (!$this->isTransactional(self::OP_INSERT)) {
570 111
            return $this->insertInternal($attributes);
571
        }
572
573
        $transaction = static::getDb()->beginTransaction();
574
        try {
575
            $result = $this->insertInternal($attributes);
576
            if ($result === false) {
577
                $transaction->rollBack();
578
            } else {
579
                $transaction->commit();
580
            }
581
582
            return $result;
583
        } catch (\Exception $e) {
584
            $transaction->rollBack();
585
            throw $e;
586
        } catch (\Throwable $e) {
587
            $transaction->rollBack();
588
            throw $e;
589
        }
590
    }
591
592
    /**
593
     * Inserts an ActiveRecord into DB without considering transaction.
594
     * @param array|null $attributes list of attributes that need to be saved. Defaults to `null`,
595
     * meaning all attributes that are loaded from DB will be saved.
596
     * @return bool whether the record is inserted successfully.
597
     */
598 111
    protected function insertInternal($attributes = null)
599
    {
600 111
        if (!$this->beforeSave(true)) {
601
            return false;
602
        }
603 111
        $values = $this->getDirtyAttributes($attributes);
604 111
        if (($primaryKeys = static::getDb()->schema->insert(static::tableName(), $values)) === false) {
605
            return false;
606
        }
607 111
        foreach ($primaryKeys as $name => $value) {
608 100
            $id = static::getTableSchema()->columns[$name]->phpTypecast($value);
609 100
            $this->setAttribute($name, $id);
610 100
            $values[$name] = $id;
611
        }
612
613 111
        $changedAttributes = array_fill_keys(array_keys($values), null);
614 111
        $this->setOldAttributes($values);
615 111
        $this->afterSave(true, $changedAttributes);
616
617 111
        return true;
618
    }
619
620
    /**
621
     * Saves the changes to this active record into the associated database table.
622
     *
623
     * This method performs the following steps in order:
624
     *
625
     * 1. call [[beforeValidate()]] when `$runValidation` is `true`. If [[beforeValidate()]]
626
     *    returns `false`, the rest of the steps will be skipped;
627
     * 2. call [[afterValidate()]] when `$runValidation` is `true`. If validation
628
     *    failed, the rest of the steps will be skipped;
629
     * 3. call [[beforeSave()]]. If [[beforeSave()]] returns `false`,
630
     *    the rest of the steps will be skipped;
631
     * 4. save the record into database. If this fails, it will skip the rest of the steps;
632
     * 5. call [[afterSave()]];
633
     *
634
     * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
635
     * [[EVENT_AFTER_VALIDATE]], [[EVENT_BEFORE_UPDATE]], and [[EVENT_AFTER_UPDATE]]
636
     * will be raised by the corresponding methods.
637
     *
638
     * Only the [[dirtyAttributes|changed attribute values]] will be saved into database.
639
     *
640
     * For example, to update a customer record:
641
     *
642
     * ```php
643
     * $customer = Customer::findOne($id);
644
     * $customer->name = $name;
645
     * $customer->email = $email;
646
     * $customer->update();
647
     * ```
648
     *
649
     * Note that it is possible the update does not affect any row in the table.
650
     * In this case, this method will return 0. For this reason, you should use the following
651
     * code to check if update() is successful or not:
652
     *
653
     * ```php
654
     * if ($customer->update() !== false) {
655
     *     // update successful
656
     * } else {
657
     *     // update failed
658
     * }
659
     * ```
660
     *
661
     * @param bool $runValidation whether to perform validation (calling [[validate()]])
662
     * before saving the record. Defaults to `true`. If the validation fails, the record
663
     * will not be saved to the database and this method will return `false`.
664
     * @param array|null $attributeNames list of attributes that need to be saved. Defaults to `null`,
665
     * meaning all attributes that are loaded from DB will be saved.
666
     * @return int|false the number of rows affected, or false if validation fails
667
     * or [[beforeSave()]] stops the updating process.
668
     * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
669
     * being updated is outdated.
670
     * @throws \Throwable in case update failed.
671
     */
672 41
    public function update($runValidation = true, $attributeNames = null)
673
    {
674 41
        if ($runValidation && !$this->validate($attributeNames)) {
675
            Yii::info('Model not updated due to validation error.', __METHOD__);
676
            return false;
677
        }
678
679 41
        if (!$this->isTransactional(self::OP_UPDATE)) {
680 41
            return $this->updateInternal($attributeNames);
681
        }
682
683
        $transaction = static::getDb()->beginTransaction();
684
        try {
685
            $result = $this->updateInternal($attributeNames);
686
            if ($result === false) {
687
                $transaction->rollBack();
688
            } else {
689
                $transaction->commit();
690
            }
691
692
            return $result;
693
        } catch (\Exception $e) {
694
            $transaction->rollBack();
695
            throw $e;
696
        } catch (\Throwable $e) {
697
            $transaction->rollBack();
698
            throw $e;
699
        }
700
    }
701
702
    /**
703
     * Deletes the table row corresponding to this active record.
704
     *
705
     * This method performs the following steps in order:
706
     *
707
     * 1. call [[beforeDelete()]]. If the method returns `false`, it will skip the
708
     *    rest of the steps;
709
     * 2. delete the record from the database;
710
     * 3. call [[afterDelete()]].
711
     *
712
     * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
713
     * will be raised by the corresponding methods.
714
     *
715
     * @return int|false the number of rows deleted, or `false` if the deletion is unsuccessful for some reason.
716
     * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
717
     * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
718
     * being deleted is outdated.
719
     * @throws \Throwable in case delete failed.
720
     */
721 7
    public function delete()
722
    {
723 7
        if (!$this->isTransactional(self::OP_DELETE)) {
724 7
            return $this->deleteInternal();
725
        }
726
727
        $transaction = static::getDb()->beginTransaction();
728
        try {
729
            $result = $this->deleteInternal();
730
            if ($result === false) {
731
                $transaction->rollBack();
732
            } else {
733
                $transaction->commit();
734
            }
735
736
            return $result;
737
        } catch (\Exception $e) {
738
            $transaction->rollBack();
739
            throw $e;
740
        } catch (\Throwable $e) {
741
            $transaction->rollBack();
742
            throw $e;
743
        }
744
    }
745
746
    /**
747
     * Deletes an ActiveRecord without considering transaction.
748
     * @return int|false the number of rows deleted, or `false` if the deletion is unsuccessful for some reason.
749
     * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
750
     * @throws StaleObjectException
751
     */
752 7
    protected function deleteInternal()
753
    {
754 7
        if (!$this->beforeDelete()) {
755
            return false;
756
        }
757
758
        // we do not check the return value of deleteAll() because it's possible
759
        // the record is already deleted in the database and thus the method will return 0
760 7
        $condition = $this->getOldPrimaryKey(true);
761 7
        $lock = $this->optimisticLock();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $lock is correct as $this->optimisticLock() targeting yii\db\BaseActiveRecord::optimisticLock() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
762 7
        if ($lock !== null) {
0 ignored issues
show
introduced by
The condition $lock !== null is always false.
Loading history...
763 1
            $condition[$lock] = $this->$lock;
764
        }
765 7
        $result = static::deleteAll($condition);
766 7
        if ($lock !== null && !$result) {
0 ignored issues
show
introduced by
The condition $lock !== null is always false.
Loading history...
767 1
            throw new StaleObjectException('The object being deleted is outdated.');
768
        }
769 7
        $this->setOldAttributes(null);
770 7
        $this->afterDelete();
771
772 7
        return $result;
773
    }
774
775
    /**
776
     * Returns a value indicating whether the given active record is the same as the current one.
777
     * The comparison is made by comparing the table names and the primary key values of the two active records.
778
     * If one of the records [[isNewRecord|is new]] they are also considered not equal.
779
     * @param ActiveRecord $record record to compare to
780
     * @return bool whether the two active records refer to the same row in the same database table.
781
     */
782 3
    public function equals($record)
783
    {
784 3
        if ($this->isNewRecord || $record->isNewRecord) {
785 3
            return false;
786
        }
787
788 3
        return static::tableName() === $record->tableName() && $this->getPrimaryKey() === $record->getPrimaryKey();
789
    }
790
791
    /**
792
     * Returns a value indicating whether the specified operation is transactional in the current [[$scenario]].
793
     * @param int $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]].
794
     * @return bool whether the specified operation is transactional in the current [[scenario]].
795
     */
796 128
    public function isTransactional($operation)
797
    {
798 128
        $scenario = $this->getScenario();
799 128
        $transactions = $this->transactions();
800
801 128
        return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation);
802
    }
803
}
804