johmanx10 / transaction
处理带有自动回滚机制的操作。
Requires
- php: ^8.0
- ext-spl: @stable
- psr/event-dispatcher: ^1.0
- psr/log: ^1.0 || ^2.0 || ^3.0
Requires (Dev)
- ext-xdebug: @stable
- php-parallel-lint/php-parallel-lint: @stable
- phpmd/phpmd: @stable
- phpro/grumphp: @stable
- phpstan/phpstan: @stable
- phpunit/phpunit: @stable
- squizlabs/php_codesniffer: @stable
- symfony/console: @stable
- symfony/event-dispatcher: @stable
Suggests
- symfony/event-dispatcher: To apply the logging event subscriber implementation.
This package is auto-updated.
Last update: 2024-09-26 03:24:25 UTC
README
简介
事务处理带有自动回滚机制的操作。
事务由操作组成。当操作失败时,它会回溯到链的顶部,以相反的顺序回滚所有之前的操作。
假设有一个需要自动化的文件系统操作的情况。如果操作的一部分失败,则需要将文件系统恢复到所有操作应用之前的状态。给定以下操作
- 创建目录
my-app
- 将文件
dist/console
复制到my-app/bin/console
- 为
my-app/bin/console
添加可执行权限
这将按以下方式处理
- ✔ 创建目录
my-app
。 - ∴ 复制文件
dist/console
到my-app/bin/console
- 目录my-app/bin
不存在。 - ✔ 回滚:如果
my-app/bin/console
存在,则删除它。 - ✔ 回滚:如果
my-app
存在,则删除它。
可以通过在命令行终端运行 examples/file-operations
来本地测试上述 示例。
每个操作都负责定义自己的回滚机制。这样,可以垂直构建复杂的嵌套结构来检查和回滚操作。
安装
composer require johmanx10/transaction
处理操作
要处理一系列有序操作,可以使用事务或处理器。
事务
事务更加直接,更适合简单的交易脚本。
<?php use Johmanx10\Transaction\Transaction; use Johmanx10\Transaction\OperationInterface; use Johmanx10\Transaction\Exception\TransactionRolledBackException; use Johmanx10\Transaction\Exception\FailedRollbackException; use Johmanx10\Transaction\Visitor\OperationVisitorInterface; /** @var OperationInterface[] $operations */ $transaction = new Transaction(...$operations); try { /** @var OperationVisitorInterface[] $visitors */ $transaction->commit(...$visitors); } catch (TransactionRolledBackException $rollback) { // Do something with the operations that were rolled back. // This exception contains a method to get all failed operations, paired // with any exception that triggered the rollback. } catch (FailedRollbackException $rollbackException) { // Do something if an operation could not be rolled back. // This exception contains the affected operation, as well as a list of // operations that have successfully rolled back up to the point where the // current operation could not. }
操作处理器
操作处理器更适合于面向服务的应用程序。它允许在调用代码之外准备访问者,从而将操作及其访问者的关注点分离。
<?php use Johmanx10\Transaction\Exception\OperationExceptionInterface; use Johmanx10\Transaction\OperationHandler; use Johmanx10\Transaction\OperationInterface; use Johmanx10\Transaction\Visitor\LogOperationVisitor; use Psr\Log\LoggerInterface; $handler = new OperationHandler(); $handler->attachVisitor( /** @var LoggerInterface $logger */ new LogOperationVisitor($logger) );; try { /** @var OperationInterface[] $operations */ $handler->handle(...$operations); } catch (OperationExceptionInterface $exception) { // Get the formatted internal exception. $formatted = $exception->getMessage(); // Get the operation that caused the exception. $operation = $exception->getOperation(); // Get the exception that shows the context of the failure. $context = $exception->getPrevious(); }
操作异常移除了很多由不同的 异常格式化程序引起的样板代码。
通过运行 以下工作示例
examples/operation-handler
定义一个操作
要创建一个操作,实现 OperationInterface
、DescribableOperationInterface
或使用现有的 Operation
类创建内联操作
<?php use Johmanx10\Transaction\Operation; use Johmanx10\Transaction\Transaction; $appDir = __DIR__ . '/my-app'; $transaction = new Transaction( // Create the app directory. new Operation( // Create the new directory. function () use ($appDir) { if (!file_exists($appDir) && !@mkdir($appDir)) { throw new RuntimeException( sprintf('Could not create directory: "%s"', $appDir) ); } }, // Roll back the operation. function () use ($appDir) { if (file_exists($appDir) && !@rmdir($appDir)) { throw new RuntimeException( sprintf('Could not remove directory: "%s"', $appDir) ); } }, // Set the operation description. sprintf('Create directory: "%s"', $appDir) ) );
格式化操作和异常
为了更好地识别操作、操作失败或特定异常,提供了一些格式化程序来帮助调试失败的操作、回滚操作的链或失败的回滚。
操作格式化程序
操作格式化程序可以用于格式化操作。如果操作实现了 DescribableOperationInterface
,则可以将其转换为字符串并按此方式表示。否则,它将创建一个通用表示,为操作提供一个唯一的标识符。
<?php use Johmanx10\Transaction\OperationInterface; use Johmanx10\Transaction\Formatter\OperationFormatterInterface; /** * @var OperationFormatterInterface $formatter * @var OperationInterface $operation */ $formatter->format($operation);
操作失败格式化程序
操作失败由操作和可选的异常组成。
当格式化操作失败时,它根据是否设置了异常来确定策略。
如果设置了异常,结果将标记为 ∴
并使用异常消息作为描述。如果没有异常,则结果将标记为 ✔
并使用格式化的操作作为描述。
操作失败使用以下模式进行格式化
({operationId}){padding} {icon} {description}
依次展示带有和没有异常的操作失败
(2) ∴ Could not copy "dist/console" -> "my-app/bin/console".
(1) ✔ Create directory: "my-app"
回滚格式化器
回滚格式化器可以用来格式化捕获到的TransactionRolledBackException
实例。
<?php use Johmanx10\Transaction\Transaction; use Johmanx10\Transaction\OperationInterface; use Johmanx10\Transaction\Exception\TransactionRolledBackException; use Johmanx10\Transaction\Formatter\RollbackFormatter; /** @var OperationInterface[] $operations */ $transaction = new Transaction(...$operations); try { $transaction->commit(); } catch (TransactionRolledBackException $rollback) { $formatter = new RollbackFormatter(); echo $formatter->format($rollback) . PHP_EOL; }
如果上面的代码尝试处理3个操作,但在第二个操作时遇到问题,格式化后的输出可能看起来像这样
2 operations were rolled back: 6, 2
Stacktrace:
(6) ∴ Could not copy "dist/console" -> "my-app/bin/console".
(2) ✔ Create directory: "my-app"
这表明第一个操作(2)成功,第二个操作(6)失败。那时操作是按照相反的顺序回滚的。
运行以下命令以查看一个工作示例
examples/file-operations
失败的回滚格式化器
当操作回滚并且在过程中某个操作在回滚时中断,将抛出FailedRollbackException
。可以使用失败的回滚格式化器进行格式化
<?php use Johmanx10\Transaction\Transaction; use Johmanx10\Transaction\OperationInterface; use Johmanx10\Transaction\Exception\FailedRollbackException; use Johmanx10\Transaction\Formatter\FailedRollbackFormatter; /** @var OperationInterface[] $operations */ $transaction = new Transaction(...$operations); try { $transaction->commit(); } catch (FailedRollbackException $rollback) { $formatter = new FailedRollbackFormatter(); echo $formatter->format($rollback) . PHP_EOL; }
当按顺序执行操作Foo
、Bar
、Baz
和Qux
,且操作在Qux
处中断时,回滚从Qux
开始并向上移动。如果对Bar
的回滚失败,格式化后的输出可能看起来像这样
Failed rolling back operation #5
Operation Bar
Could not rollback Bar.
Previous rollbacks:
(10) ∴ Failed operation Qux
(8) ✔ Operation Baz
这表明Qux
的操作中断了链。可能成功回滚Baz
,但Bar
无法回滚,因此Foo
在这个画面中完全缺失,因为从未尝试对Foo
进行回滚。
异常使用以下格式
Failed rolling back operation #{operationId}
{operationDescription}
{rollbackExceptionMessage}
如果有之前的回滚,则附加以下内容
Previous rollbacks:
{previousRollbacks}
访问操作
Transaction
的默认实现实现了接口\Johmanx10\Transaction\Visitor\AcceptingTransactionInterface
,允许它接受操作访问者,实现了\Johmanx10\Transaction\Visitor\OperationVisitorInterface
。
这可以用来收集在事务提交期间执行的操作的信息。
以下展示了如何在事务中记录即将执行的每个操作
<?php use Johmanx10\Transaction\OperationInterface; use Johmanx10\Transaction\Transaction; use Johmanx10\Transaction\Visitor\LogOperationVisitor; use Psr\Log\LoggerInterface; /** @var LoggerInterface $logger */ $visitor = new LogOperationVisitor($logger); /** @var OperationInterface[] $operations */ $transaction = new Transaction(...$operations); $transaction->commit($visitor);