inneair / transaction-bundle
Symfony 的事务包
Requires
- php: >=5.6
- doctrine/orm: ~2.5
- jms/aop-bundle: ~1.2
- psr/log: ~1.0
- symfony/config: ~3.0
- symfony/dependency-injection: ~3.0
- symfony/doctrine-bridge: ~3.0
- symfony/http-kernel: ~3.0
Requires (Dev)
- phpunit/phpunit: ~6.0
- satooshi/php-coveralls: ~1.0
This package is not auto-updated.
Last update: 2024-09-23 07:31:46 UTC
README
此包提供了一种使用注解管理事务的简单方法。它深受 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 配置参考)。 然而,我们强烈建议避免使用嵌套事务。组件应设计为使用required
或not-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
注解是设计可靠业务服务的关键因素,同时具有低编码工作量。