inneair/transaction-bundle

Symfony 的事务包

安装次数: 13,083

依赖项: 0

建议者: 0

安全性: 0

星级: 10

关注者: 5

分支: 3

类型:symfony-bundle

1.0.3 2017-07-31 12:38 UTC

This package is not auto-updated.

Last update: 2024-09-23 07:31:46 UTC


README

Build status Coverage status SensioLabs Insight

Latest stable version Latest unstable version License

Scrutinizer Code Quality Code Coverage Build Status

此包提供了一种使用注解管理事务的简单方法。它深受 JMSAopBundle 提供的示例所启发,但提供了以下功能:

  • 可配置的策略,允许类/方法选择是否必须打开新事务,或者根本不存在。
  • 事务上下文的继承,从标注的类/方法到另一个。
  • 可配置的异常列表,当方法调用完成时,带有异常不会触发回滚。

为什么使用此包而不是Doctrine-ORM实体管理器的标准行为?

如果我们深入研究Doctrine-ORM实体管理器的代码,我们可以看到实体管理器已经支持递归调用方法 begin_transaction,并且为顶层调用真正启动了事务。然而,这意味着这种行为 - 确定是否应该打开事务 - 被编码在持久化层中。事务应由业务组件管理,它们能够声明期望的行为。ORM 是持久化层的一部分,不应意识到这一点。

摘要

安装

1. 下载

可以使用 Composer 在您的 Symfony 项目中安装此包。打开命令行界面,进入项目目录并执行以下命令以下载此包的最新稳定版本

composer require inneair/transaction-bundle

2. 激活

首先按照 这些说明 安装和配置 JMSAopBundle

通过修改 app/AppKernel.php 文件来激活包

<?php
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...
            new Inneair\TransactionBundle\InneairTransactionBundle(),
        );

        // ...
    }

    // ...
}

配置

1. YAML 配置

inneair_transaction:
    strict_mode: false
    default_policy: required
    no_rollback_exceptions:
        - 'Company\\Bundle\\MyException1'
        - 'Company\\Bundle\\MyException2'

2. XML 配置

<?xml version="1.0" ?>
<container xmlns="https://symfony.com.cn/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:inneair-transaction="http://example.org/schema/dic/inneair_transaction"
    xsi:schemaLocation="https://symfony.com.cn/schema/dic/services https://symfony.com.cn/schema/dic/services/services-1.0.xsd
        inneair_transaction http://example.org/dic/schema/inneair_transaction/transaction-1.0.xsd">

    <inneair-transaction:config strict-mode="false" default-policy="required">
        <inneair-transaction:no-rollback-exception>Company\Bundle\MyException1</Inneair-transaction:no-rollback-exception>
        <inneair-transaction:no-rollback-exception>Company\Bundle\MyException2</Inneair-transaction:no-rollback-exception>
    </inneair-transaction:config>
</container>

3. 参考

strict_mode: 是否必须实现 TransactionalAwareInterface 接口,以便读取 @Transactional 注解。默认值为 false

default_policy: 未在注解中设置策略时的默认事务策略,必须是以下值之一

  • required: 如果不存在事务上下文,则必须启动事务。
  • not-required: 无论是否存在现有的事务上下文,都不必启动事务。
  • nested: 即使已存在事务上下文,也必须启动新事务(使用保存点)。必须在连接参数中启用保存点(请参阅 DoctrineBundle 配置参考)。 然而,我们强烈建议避免使用嵌套事务。组件应设计为使用 requirednot-required 策略。

默认值为 required

no_rollback_exceptions: 不触发回滚的异常的完整限定类名数组,如果事务上下文仍然存在。应异常使用此参数。默认为空数组。

使用

在任何容器管理的组件内部,插入注解@Transaction以启用自动事务管理。当注解中定义了参数时,它将覆盖捆绑配置中的默认值。

警告:保持关注点分离,以及解耦的组件。虽然控制器的交易管理在示例中经常展示,但这不是一个好的设计方向。Web应用中的控制器是HTTP接口,它们的基本作用是接收请求、调用业务服务、返回响应:根据定义,它们属于表示层。事务应由业务组件管理。

1. 整个服务

注解可以插入到类本身的PHP文档块中,以实现对所有公共方法的事务管理。所有注解参数都是可选的。如果没有指定,则应用捆绑的默认配置(见上文)。

use Exception;
use MyCompany\MyBundle\MyException;
use Inneair\TransactionBundle\Annotation\Transactional;

/**
 * @Transactional(policy=Transactional::REQUIRED, noRollbackExceptions={Exception::class, MyException::class})
 */
class AccountManager
{
    // ...
}

2. 单个方法

注解可以插入到公共方法本身的PHP文档块中,以允许事务管理。注解参数是可选的。如果没有指定,则应用捆绑的默认配置(见上文)。

use Exception;
use MyCompany\MyBundle\MyException;
use Inneair\TransactionBundle\Annotation\Transactional;

class AccountManager
{
    /**
     * @Transactional(policy=Transactional::REQUIRED, noRollbackExceptions={Exception::class, MyException::class})
     */
    public function createAccount(Account $account)
    {
        // ...
    }
}

一个具体示例

让我们想象一个处理人力资源以及人员在众多公司之间组织应用。一个很好的策略(在其他策略中)是实施

  • 一个用于管理公司的业务服务 CompanyManager
  • 一个用于管理这些公司中人员的管理服务 PersonManager

让我们关注以下用户故事: "我想在一步之内注册一个新公司和其中的人员"

1. Company 模型

公司位于我们的业务模型的最顶层,它是独立的(没有依赖)。

<?php

namespace Inneair\Demo\Model;

class Company
{
    /**
     * ID of the company.
     * @var integer
     */
    private $id;
    /**
     * Name of the company.
     * @var string
     */
    private $name;
    
    // Let's imagine there are a getter/setter for all properties.
    // ...
}

2. Person 模型

人员也是一个非常简单的概念,但在我们的应用中,一个人总是属于一个公司。因此,模型应包含依赖关系。

<?php

namespace Inneair\Demo\Model;

class Person
{
    /**
     * ID of the person.
     * @var integer
     */
    private $id;
    /**
     * First name of the person.
     * @var string
     */
    private $firstName;
    /**
     * The company the person belongs to.
     * @var Company
     */
    private $company;

    // Let's imagine there are a getter/setter for all properties.
    // ...
}

3. CompanyManager 服务

当另一个组件(比如说HTTP控制器)需要添加一个新公司时,这必须以原子性、一致性、隔离性和持久性(ACID原则)的方式进行。事务是一个合适的解决方案!使用@Transactional注解,每次组件调用addCompany方法时,都会打开一个新的事务上下文。如果在该方法中出现问题(例如异常),则持久层中已完成的所有操作将自动回滚,系统保持一致性状态。否则,事务将提交。

<?php

namespace Inneair\Demo\Service;

use Inneair\TransactionBundle\Annotation\Transactional;

class CompanyManager implements CompanyManagerInterface
{
    /**
     * Adds a company.
     *
     * @param Company $company The company.
     * @return Company The new company.
     * @throws BusinessException If an existing company has already the same name.
     * @Transactional
     */
    public function addCompany(Company $company)
    {
        // Check there is not another company with the same name in the repository, or throw a business exception!
        // -> probably a request to the persistence layer...

        // Insert the company in a repository.
        // -> probably a request to the persistence layer...

        return $company;
    }
}

4. PersonManager 服务

当另一个组件(比如说HTTP控制器)需要在一个新公司中添加一个新人员时,这也必须以原子性、一致性、隔离性和持久性(ACID原则)的方式进行。如果公司已创建,但人员无法创建,我们希望所有操作都回滚。也需要一个事务,因此让我们为addPerson方法添加@Transactional注解。

<?php

namespace Inneair\Demo\Service;

use Inneair\TransactionBundle\Annotation\Transactional;

class PersonManager implements PersonManagerInterface
{
    /**
     * Company manager.
     * @var CompanyManagerInterface
     */
    private $companyManager;

    /**
     * Adds a person.
     *
     * @param Person $person The person.
     * @return Person The new person.
     * @Transactional
     */
    public function addPerson(Person $person)
    {
        // Add the company.
        $person->setCompany($this->companyManager->addCompany($person->getCompany()));

        // Insert the person in a repository
        // -> probably a request to the persistence layer...
        
        return $person;
    }
}

到此为止,你可能注意到打开了两个事务

  • 一个是在调用addPerson时打开的,
  • 一个是在addPerson方法内部调用addCompany时打开的。

实际上,绝对没有,这正是这个捆绑包真正有用的原因。使用默认的required策略,我们实际上指定只有在没有活动的事务上下文的情况下才打开事务。因此,在addPerson方法的交易上下文中调用addCompany时不会打开新的事务。事务上下文将自动继承。另一方面,如果addCompany在交易上下文外部调用,则会打开新的事务。

使用@Transactional注解是设计可靠业务服务的关键因素,同时具有低编码工作量。