In modern databases, transactions also do some other things, such as ensuring that you can't access data that another person has written halfway. However, the basic idea is the same—transactions are there to ensure that no matter what happens, the data you work with will be in a sensible state. They guarantee that there will not be a situation where money is withdrawn from one account, but not deposited to another.
Yii2 supports a powerful transaction mechanism with savepoints.
A classic example is of transferring money from one bank account to another. To do that, you have to first withdraw the amount from the source account, and then deposit it to the destination account. The operation has to succeed in full. If you stop halfway, the money will be lost, and that is very bad. For instance, we have a recipient account and a sender account. We would like to transfer money from sender to recipient. Let's assume that we have an account model.
Our account model will be very simple and it will contain only the id
and balance
fields.
./yii migrate/create create_account_table
<?php use yiidbSchema; use yiidbMigration; class m150620_062034_create_account_table extends Migration { const TABLE_NAME = '{{%account}}'; public function up() { $tableOptions = null; if ($this->db->driverName === 'mysql') { $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB'; } $this->createTable(self::TABLE_NAME, [ 'id' => Schema::TYPE_PK, 'balance' => ' NUMERIC(15,2) DEFAULT NULL', ], $tableOptions); } public function down() { $this->dropTable(self::TABLE_NAME); } }
./yii migrate up
Account
models with balance for our table:./yii migrate/create add_account_records
<?php use yiidbMigration; use appmodelsAccount; class m150620_063252_add_account_records extends Migration { public function up() { $accountFirst = new Account(); $accountFirst->balance = 1110; $accountFirst->save(); $accountSecond = new Account(); $accountSecond->balance = 779; $accountSecond->save(); $accountThird = new Account(); $accountThird->balance = 568; $accountThird->save(); return true; } public function down() { $this->truncateTable('{{%account}}'); return false; } }
rules
method, to models/Account.php
:public function rules() { return [ //.. [['balance'], 'number', 'min' => 0], //.. ]; }
TestController
with success and error actions:<?php namespace appcontrollers; use appmodelsAccount; use Yii; use yiidbException; use yiihelpersHtml; use yiihelpersVarDumper; use yiiwebController; class TestController extends Controller { public function actionSuccess() { $transaction = Yii::$app->db->beginTransaction(); try { $recipient = Account::findOne(1); $sender = Account::findOne(2); $transferAmount = 177; $recipient->balance += $transferAmount; $sender->balance -= $transferAmount; if ($sender->save() && $recipient->save()) { $transaction->commit(); return $this->renderContent( Html::tag('h1', 'Money transfer was successfully') ); } else { $transaction->rollBack(); throw new Exception('Money transfer failed:' . VarDumper::dumpAsString($sender->getErrors()) . VarDumper::dumpAsString($recipient->getErrors()) ); } } catch ( Exception $e ) { $transaction->rollBack(); throw $e; } } public function actionError() { $transaction = Yii::$app->db->beginTransaction(); try { $recipient = Account::findOne(1); $sender = Account::findOne(3); $transferAmount = 1000; $recipient->balance += $transferAmount; $sender->balance -= $transferAmount; if ($sender->save() && $recipient->save()) { $transaction->commit(); return $this->renderContent( Html::tag('h1', 'Money transfer was successfully') ); } else { $transaction->rollBack(); throw new Exception('Money transfer failed: ' . VarDumper::dumpAsString($sender->getErrors()) . VarDumper::dumpAsString($recipient->getErrors()) ); } } catch ( Exception $e ) { $transaction->rollBack(); throw $e; } } }
test/success
and you should get the output shown in the following screenshot:test/erro
r and you should get the output shown in the following screenshot:As you will remember, we added a rule to the Account
model, so our account balance can be only positive. The transaction will roll back in this case and it prevents a situation where money is withdrawn from a sender's account but not deposited to the recipient's account.
3.129.23.30