niclas-van-eyk/laravel-transactional-controllers

轻松将数据库事务包装到控制器操作中。

v0.2.1 2023-09-28 16:13 UTC

README

轻松将数据库事务包装到控制器操作中。

Latest Version on Packagist Total Downloads

class ExampleUsageController
{
    #[Transactional]
    public function demo(Request $request)
    {
        User::create($request->all());
        User::create($request->all());

        throw new Exception("Everything will be rolled back!");
    }
}

安装

您可以通过composer安装此包

composer require niclas-van-eyk/laravel-transactional-controllers

背景

如果您想对数据库进行一系列编辑,要么 全部 同时发生,要么 完全不发生,通常您会使用数据库事务。这里我们使用的例子是一个用户($author)向另一个用户($receiver)转移一定的$amount。我们还想在单独的模型(TransferLog)中保存这个转账已经发生的事实。

用法

在之前,您可能已经编写了如下代码

namespace App\Http\Controllers;

use App\Http\Requests\TransferMoneyRequest;
use App\Models\TransferLog;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class BankAccountController 
{
    public function transferMoney(TransferMoneyRequest $request)
    {
        return DB::transaction(function () use ($request) {
            $request->author->balance->decrement($request->amount);
            $request->receiver->balance->increment($request->amount);

            return TransferLog::createFromTransferRequest($request);
        })
    }
}

您必须将整个代码包裹在一个大的闭包中,显式地 use 所有注入的参数,如果您想在事务闭包的 内部 返回某些东西,您最终会得到这种双重返回,使得代码更难以阅读,并让您的IDE感到不满。

laravel-transactional-controllers 通过消除需要在闭包中包装代码的需要,并且通过在控制器方法上添加 Transactional 属性来解决此问题

namespace App\Http\Controllers;

use App\Http\Requests\TransferMoneyRequest;
use App\Models\TransferLog;
use Illuminate\Http\Request;
use NiclasVanEyk\TransactionalControllers\Transactional; // <-- from this package

class BankAccountController 
{
    #[Transactional]
    public function transferMoney(TransferMoneyRequest $request): TransferLog
    {
        $request->author->balance->decrement($request->amount);
        $request->receiver->balance->increment($request->amount);

        return TransferLog::createFromTransferRequest($request);
    }
}

不再需要 use、双重 return 或您的IDE对无法保证正确返回类型的抱怨!

您还可以显式指定用于运行事务的数据库连接(默认使用 config('database.default')

    #[Transactional(connection: 'other')]
    public function store() {}

限制

此功能仅在控制器中使用时有效

use NiclasVanEyk\TransactionalControllers\Transactional;

// Works ✅
class RegularController
{
    #[Transactional] 
    public function store() {}
}
Route::post('/regular-store', [RegularController::class, 'store']);

// Works ✅
class InvokableController
{
    #[Transactional]
    public function __invoke() {}
}
Route::post('/invokable-store', InvokableController::class);

// Does not work ❌
Route::post(
    '/invokable-store', 
    #[Transactional]
    function () { /* Will not open a transaction! */},
)

实现细节

此包使用Laravel的 ControllerDispatcher 组件,该组件确定控制器操作应该如何执行。这意味着我们可以将打开事务推迟到最后时刻,防止 打开不必要的交易!例如,如果 FormRequest 内部的验证失败,或者使用路由模型绑定找不到模型,则不会开始事务。

更新日志

有关最近更改的更多信息,请参阅 CHANGELOG

贡献

如果您有任何修改的想法,请随意提出问题、PR或分支项目。

本地开发

这假设您已经在本地安装了sqlite、PHP以及所有composer依赖。

运行测试

composer test

运行格式化器

composer fix-cs

运行分析

composer analyse

一次性运行上述所有操作

composer ci

致谢

许可

MIT许可(MIT)。有关更多信息,请参阅 许可文件