模型事务¶
概览¶
当一个进程执行多个数据库操作时,将所有这些操作作为一个工作单元来执行是非常重要的。这样,如果其中任何一个操作失败,我们不会得到损坏的数据或孤立的记录。数据库事务提供了这种功能,并确保在将数据存储到数据库之前成功执行了所有数据库操作。
Phalcon 中的事务允许您在所有操作成功执行时提交所有操作,或者在出现问题时回滚所有操作。
手动¶
如果应用程序仅使用一个连接且事务不复杂,可以通过在连接上开始事务并在一切正常时提交事务或回滚事务来创建事务:
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Db\Adapter\Pdo\Mysql;
/**
 * @property Mysql $db
 */
class InvoicesController extends Controller
{
    public function saveAction()
    {
        $this->db->begin();
        try {
            $customer = Customers::findFirst(
                [
                    'conditions' => 'cst_id = :cst_id:',
                    'bind'       => [
                        'cst_id' => 10,
                    ]    
                ]  
            );
            $customer->cst_has_unpaid = true;
            $result = $customer->save();
            if (false === $result) {
                throw new \Exception('Error saving file');
            }
            $invoice = new Invoices();
            $invoice->inv_cst_id     = $customer->cst_id;
            $invoice->inv_number     = 'INV-00001';
            $invoice->inv_name       = 'Invoice for Goods';
            $invoice->inv_created_at = date('Y-m-d');
            $result = $invoice->save();
            if (false === $result) {
                throw new \Exception('Error saving file');
            }
            $this->db->commit();
        } catch (\Exception $ex) {
            $this->db->rollback();
            echo $ex->getMessage();
        }
    }
}
隐式¶
可以使用现有关系来存储记录及其相关实例。像这样的操作会隐式创建事务以确保数据正确存储:
<?php
use MyApp\Models\Invoices;
use MyApp\Models\Customers;
$invoice = new Invoices();
$invoice->inv_cst_id     = $customer->cst_id;
$invoice->inv_number     = 'INV-00001';
$invoice->inv_name       = 'Invoice for Goods';
$invoice->inv_created_at = date('Y-m-d');
$customer = new Customers();
$customer->cst_name       = 'John Wick';
$customer->cst_has_unpaid = true;
$customer->invoices       = $invoice;
$customer->save();
隔离¶
隔离事务在新连接中执行,确保生成的所有 SQL、虚拟外键检查和业务规则与主连接隔离。这种事务需要一个事务管理器,该管理器全局管理每个创建的事务,确保在请求结束前正确回滚或提交:
<?php
use Phalcon\Mvc\Model\Transaction\Failed as TxFailed;
use Phalcon\Mvc\Model\Transaction\Manager as TxManager;
$manager = new TxManager();
$transaction = $manager->get();
try {
    $customer = Customers::findFirst(
        [
            'conditions' => 'cst_id = :cst_id:',
            'bind'       => [
                'cst_id' => 10,
            ]    
        ]  
    );
    $customer->cst_has_unpaid = true;
    $result = $customer->save();
    if (false === $result) {
        throw new \Exception('Error saving file');
    }
    $invoice = new Invoices();
    $invoice->inv_cst_id     = $customer->cst_id;
    $invoice->inv_number     = 'INV-00001';
    $invoice->inv_name       = 'Invoice for Goods';
    $invoice->inv_created_at = date('Y-m-d');
    $result = $invoice->save();
    if (false === $result) {
        throw new \Exception('Error saving file');
    }
    $transaction->commit();
} catch (TxFailed $ex) {
    $transaction->rollback();
    echo $ex->getMessage();
}
可以使用事务删除多个记录,确保所有内容都被正确删除:
<?php
use Phalcon\Mvc\Model\Transaction\Failed as TxFailed;
use Phalcon\Mvc\Model\Transaction\Manager as TxManager;
$manager = new TxManager();
$transaction = $manager->get();
try {
    $invoices = Invoices::find(
        [
            'conditions' => 'inv_cst_id = :cst_id:',
            'bind'       => [
                'cst_id' => 10,
            ]    
        ]  
    );
    foreach ($invoices as $invoice) {
        $invoice->setTransaction($transaction);
        if (false === $invoice->delete()) {
            $messages = $invoice->getMessages();
            foreach ($messages as $message) {
                $transaction->rollback(
                    $message->getMessage()
                );
            }
        }
    }
    $transaction->commit();
} catch (TxFailed $ex) {
    echo $ex->getMessage();
}
异常¶
Logger 组件中抛出的任何异常都将是类型Phalcon\Mvc\Model\Transaction\Exception或Phalcon\Mvc\Model\Transaction\Failed。您可以使用这些异常来选择性地捕获仅从此组件抛出的异常。
此外,如果回滚未成功,可以使用throwRollbackException(true)方法设置容器。
<?php
use Phalcon\Mvc\Model\Transaction\Failed as TxFailed;
use Phalcon\Mvc\Model\Transaction\Manager as TxManager;
$manager = new TxManager();
$transaction = $manager
    ->get()
    ->throwRollbackException(true)
;
try {
    $invoices = Invoices::find(
        [
            'conditions' => 'inv_cst_id = :cst_id:',
            'bind'       => [
                'cst_id' => 10,
            ]    
        ]  
    );
    foreach ($invoices as $invoice) {
        $invoice->setTransaction($transaction);
        if (false === $invoice->delete()) {
            $messages = $invoice->getMessages();
            foreach ($messages as $message) {
                $transaction->rollback(
                    $message->getMessage()
                );
            }
        }
    }
    $transaction->commit();
} catch (TxFailed $ex) {
    echo $ex->getMessage();
}
依赖注入¶
无论从何处检索事务对象,事务都会被复用。只有当commit()或rollback()执行时才会生成新的事务。您可以使用服务容器为整个应用程序创建全局事务管理器:
<?php
use Phalcon\Mvc\Model\Transaction\Manager;
$container->setShared(
    'transactions',
    function () {
        return new Manager();
    }
);
然后从控制器或视图中访问它:
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Mvc\Model\Transaction\Manager;
/**
 * @property Manager $transactions
 */
class ProductsController extends Controller
{
    public function saveAction()
    {
        $manager = $this->di->getTransactions();
        $manager = $this->transactions;
        $transaction = $manager->get();
        // ...
    }
}
注意
在事务活跃期间,事务管理器将始终在整个应用程序中返回相同的事务。