Transactions

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.

Getting ready...

Our account model will be very simple and it will contain only the id and balance fields.

  1. Create a new application using the Composer package manager, as described in the official guide at http://www.yiiframework.com/doc-2.0/guide-start-installation.html.
  2. Create a migration, which adds an account table, using the following command:
    ./yii migrate/create create_account_table
  3. Also, update the just- created migration using the following code:
    <?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);
        }
    }
  4. Then, install migration with the following command:
    ./yii migrate up
  5. Use Gii to create a model for the account table.
  6. Create a migration, which adds some test Account models with balance for our table:
    ./yii migrate/create add_account_records
  7. Also, update the just-created migration using the following code:
    <?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;
        }
    }

How to do it…

  1. Add the following rule to the rules method, to models/Account.php:
    public function rules()
    {
        return [
            //..
            [['balance'], 'number', 'min' => 0],
            //..
        ];
    }
  2. Let us assume that our balance may be only positive and that it can't be negative.
  3. Create 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;
            }
        }
    }
  4. Run test/success and you should get the output shown in the following screenshot:
    How to do it…
  5. In this case, the transaction mechanism will not update the recipient and sender balance if some error occurred.
  6. Run test/error and you should get the output shown in the following screenshot:
    How to do it…

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.23.30