diff --git a/src/AbstractActiveRecord.php b/src/AbstractActiveRecord.php index 2cba5d96a..a0a67b541 100644 --- a/src/AbstractActiveRecord.php +++ b/src/AbstractActiveRecord.php @@ -288,6 +288,13 @@ public function hasOne(string $class, array $link): ActiveQueryInterface return $this->createRelationQuery($class, $link, false); } + public function insert(array $attributes = null): bool + { + return $this->insertInternal($attributes); + } + + abstract protected function insertInternal(array $attributes = null): bool; + /** * @psalm-param class-string $arClass */ diff --git a/src/ActiveRecord.php b/src/ActiveRecord.php index 5df15c5ff..c5474eb55 100644 --- a/src/ActiveRecord.php +++ b/src/ActiveRecord.php @@ -12,6 +12,7 @@ use Yiisoft\ActiveRecord\Trait\ArrayIteratorTrait; use Yiisoft\ActiveRecord\Trait\MagicPropertiesTrait; use Yiisoft\ActiveRecord\Trait\MagicRelationsTrait; +use Yiisoft\ActiveRecord\Trait\TransactionalTrait; use Yiisoft\Arrays\ArrayableInterface; use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidArgumentException; @@ -92,63 +93,20 @@ * @template-implements ArrayAccess * @template-implements IteratorAggregate */ -class ActiveRecord extends AbstractActiveRecord implements ArrayableInterface, ArrayAccess, IteratorAggregate +class ActiveRecord extends AbstractActiveRecord implements ArrayableInterface, ArrayAccess, IteratorAggregate, TransactionalInterface { use ArrayableTrait; use ArrayAccessTrait; use ArrayIteratorTrait; use MagicPropertiesTrait; use MagicRelationsTrait; - - /** - * The insert operation. This is mainly used when overriding {@see transactions()} to specify which operations are - * transactional. - */ - public const OP_INSERT = 0x01; - - /** - * The update operation. This is mainly used when overriding {@see transactions()} to specify which operations are - * transactional. - */ - public const OP_UPDATE = 0x02; - - /** - * The delete operation. This is mainly used when overriding {@see transactions()} to specify which operations are - * transactional. - */ - public const OP_DELETE = 0x04; - - /** - * All three operations: insert, update, delete. - * - * This is a shortcut of the expression: OP_INSERT | OP_UPDATE | OP_DELETE. - */ - public const OP_ALL = 0x07; + use TransactionalTrait; public function attributes(): array { return $this->getTableSchema()->getColumnNames(); } - public function delete(): int - { - if (!$this->isTransactional(self::OP_DELETE)) { - return $this->deleteInternal(); - } - - $transaction = $this->db->beginTransaction(); - - try { - $result = $this->deleteInternal(); - $transaction->commit(); - - return $result; - } catch (Throwable $e) { - $transaction->rollBack(); - throw $e; - } - } - public function filterCondition(array $condition, array $aliases = []): array { $result = []; @@ -195,42 +153,6 @@ public function getTableSchema(): TableSchemaInterface return $tableSchema; } - public function insert(array $attributes = null): bool - { - if (!$this->isTransactional(self::OP_INSERT)) { - return $this->insertInternal($attributes); - } - - $transaction = $this->db->beginTransaction(); - - try { - $result = $this->insertInternal($attributes); - if ($result === false) { - $transaction->rollBack(); - } else { - $transaction->commit(); - } - - return $result; - } catch (Throwable $e) { - $transaction->rollBack(); - throw $e; - } - } - - /** - * Returns a value indicating whether the specified operation is transactional. - * - * @param int $operation The operation to check. Possible values are {@see OP_INSERT}, {@see OP_UPDATE} and - * {@see OP_DELETE}. - * - * @return array|bool Whether the specified operation is transactional. - */ - public function isTransactional(int $operation): array|bool - { - return $this->transactions(); - } - /** * Loads default values from database table schema. * @@ -303,61 +225,6 @@ public function refresh(): bool return $this->refreshInternal($query->onePopulate()); } - /** - * Declares which DB operations should be performed within a transaction in different scenarios. - * - * The supported DB operations are: {@see OP_INSERT}, {@see OP_UPDATE} and {@see OP_DELETE}, which correspond to the - * {@see insert()}, {@see update()} and {@see delete()} methods, respectively. - * - * By default, these methods are NOT enclosed in a DB transaction. - * - * In some scenarios, to ensure data consistency, you may want to enclose some or all of them in transactions. You - * can do so by overriding this method and returning the operations that need to be transactional. For example, - * - * ```php - * return [ - * 'admin' => self::OP_INSERT, - * 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE, - * // the above is equivalent to the following: - * // 'api' => self::OP_ALL, - * - * ]; - * ``` - * - * The above declaration specifies that in the "admin" scenario, the insert operation ({@see insert()}) should be - * done in a transaction; and in the "api" scenario, all the operations should be done in a transaction. - * - * @return array The declarations of transactional operations. The array keys are scenarios names, and the array - * values are the corresponding transaction operations. - */ - public function transactions(): array - { - return []; - } - - public function update(array $attributeNames = null): int - { - if (!$this->isTransactional(self::OP_UPDATE)) { - return $this->updateInternal($attributeNames); - } - - $transaction = $this->db->beginTransaction(); - - try { - $result = $this->updateInternal($attributeNames); - if ($result === 0) { - $transaction->rollBack(); - } else { - $transaction->commit(); - } - - return $result; - } catch (Throwable $e) { - $transaction->rollBack(); - throw $e; - } - } - /** * Valid column names are table column names or column names prefixed with table name or table alias. * diff --git a/src/Trait/TransactionalTrait.php b/src/Trait/TransactionalTrait.php new file mode 100644 index 000000000..d10720e6e --- /dev/null +++ b/src/Trait/TransactionalTrait.php @@ -0,0 +1,100 @@ +isTransactional(TransactionalInterface::OP_DELETE)) { + return $this->deleteInternal(); + } + + $transaction = $this->db->beginTransaction(); + + try { + $result = $this->deleteInternal(); + $transaction->commit(); + + return $result; + } catch (Throwable $e) { + $transaction->rollBack(); + throw $e; + } + } + + public function insert(array $attributes = null): bool + { + if (!$this->isTransactional(TransactionalInterface::OP_INSERT)) { + return $this->insertInternal($attributes); + } + + $transaction = $this->db->beginTransaction(); + + try { + $result = $this->insertInternal($attributes); + if ($result === false) { + $transaction->rollBack(); + } else { + $transaction->commit(); + } + + return $result; + } catch (Throwable $e) { + $transaction->rollBack(); + throw $e; + } + } + + public function isTransactional(int $operation): bool + { + return in_array($operation, $this->transactions(), true); + } + + public function transactions(): array + { + return [ + TransactionalInterface::OP_INSERT, + TransactionalInterface::OP_UPDATE, + TransactionalInterface::OP_DELETE, + ]; + } + + public function update(array $attributeNames = null): int + { + if (!$this->isTransactional(TransactionalInterface::OP_UPDATE)) { + return $this->updateInternal($attributeNames); + } + + $transaction = $this->db->beginTransaction(); + + try { + $result = $this->updateInternal($attributeNames); + if ($result === 0) { + $transaction->rollBack(); + } else { + $transaction->commit(); + } + + return $result; + } catch (Throwable $e) { + $transaction->rollBack(); + throw $e; + } + } +} diff --git a/src/TransactionalInterface.php b/src/TransactionalInterface.php new file mode 100644 index 000000000..24d1249a9 --- /dev/null +++ b/src/TransactionalInterface.php @@ -0,0 +1,65 @@ + self::OP_INSERT, + * 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE, + * // the above is equivalent to the following: + * // 'api' => self::OP_ALL, + * + * ]; + * ``` + * + * The above declaration specifies that in the "admin" scenario, the insert operation ({@see insert()}) should be + * done in a transaction; and in the "api" scenario, all the operations should be done in a transaction. + * + * @return array The declarations of transactional operations. The array keys are scenarios names, and the array + * values are the corresponding transaction operations. + */ + public function transactions(): array; +}