johmanx10/

transaction

处理带有自动回滚机制的操作。

2.0.0-RC1 2021-07-25 16:55 UTC

This package is auto-updated.

Last update: 2024-09-26 03:24:25 UTC


README

Build Status Packagist Packagist PHP from Packagist Scrutinizer Code Quality Code Coverage Packagist

简介

事务处理带有自动回滚机制的操作。

事务由操作组成。当操作失败时,它会回溯到链的顶部,以相反的顺序回滚所有之前的操作。

假设有一个需要自动化的文件系统操作的情况。如果操作的一部分失败,则需要将文件系统恢复到所有操作应用之前的状态。给定以下操作

  1. 创建目录 my-app
  2. 将文件 dist/console 复制到 my-app/bin/console
  3. my-app/bin/console 添加可执行权限

这将按以下方式处理

  1. ✔ 创建目录 my-app
  2. ∴ 复制文件 dist/consolemy-app/bin/console - 目录 my-app/bin 不存在。
  3. ✔ 回滚:如果 my-app/bin/console 存在,则删除它。
  4. ✔ 回滚:如果 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

定义一个操作

要创建一个操作,实现 OperationInterfaceDescribableOperationInterface 或使用现有的 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;
}

当按顺序执行操作FooBarBazQux,且操作在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);