不败君

前端萌新&初级后端攻城狮

Laravel自定义错误及全局事务

Laravel自定义错误及全局事务

2020-07-16 18:22:00

围观(294)

在一些业务逻辑处理中, 肯定经常使用事务的, 而如果在每个方法都加入 try 和抛出异常则会显得代码非常杂乱. 所以博主一直都喜欢使用"全局事务".


可能有些人进来本文根本不知道什么是事务, 简单说一下, 假设有两个表 一个 user 表 一个 user_info 表, 注册的时候, 需要在 user 表写入用户的注册资料, 例如手机号 / 邮箱 和 密码这些, 而用户的 头像 / 性别 / 出生日期 可能就存在 user_info 表了. 此时如果插入数据到 user 表成功, 但中间一些代码报错了, 导致 user 表数据进去了而 user_info 数据是没存的, 这时候问题就来了.


伪代码:

public function reg()
{
    // 保存用户的手机号和密码到 user 表
    saveUser('手机号', '密码');      // 这里将用户存入了 user 表

    // code ..
    // 此时代码或者判断出了一些问题
    // 结束了运行

    saveUserInfo('头像地址', '性别', '出生日期');     // 这里没有执行 没有存用户信息进 user_info 表
}

所以解决方法就是使用数据库事务.

本文比较适合会 Laravel 的开发者看(不然可能会看得懵 博主也只是新手 写得也不好), 当然如果你有兴趣也可以往下看.


创建中间件

创建中间件

php artisan make:middleware WebBase


为路由分配中间件

在 App\Http\Kernel 类中的属性 routeMiddleware 加入刚创建好的中间件:

protected $routeMiddleware = [
    ...
    ...
    ...
    'web_base' => \App\Http\Middleware\WebBase::class,  // 刚才创建好的中间件
];


中间件开启事务

打开刚创建的中间件 WebBase 类将 handle 方法修改:

public function handle($request, Closure $next)
{
    DB::beginTransaction();     // 前置中间件 开启事务
    
    $response = $next($request);

    DB::commit();               // 后置中间件 提交事务

    return $response;
}


别忘了需要引入 DB 门面:

use Illuminate\Support\Facades\DB;


抛出异常时回滚事务

修改在 App\Exceptions 下的 Handler 类的 render 方法:

public function render($request, Throwable $exception)
{
    DB::rollBack();     // 回滚事务
    return parent::render($request, $exception);
}


自定义错误

假设需要开发 API 接口, 就需要定义一个 API 接口的错误信息, 则需要在 App\Exceptions 下的 Handler 类的 render 方法修改成这样:

public function render($request, Throwable $exception)
{
    DB::rollBack();

    // \App\Exceptions\ApiError 可以使用命名空间引入 就不需要写这么长的
    if ($exception instanceof \App\Exceptions\ApiError)  {
        return $exception->render($request);
    }

    return parent::render($request, $exception);
}

接着在 App\Exceptions 下创建一个 ApiError 类并写入:

<?php

namespace App\Exceptions;

use Exception;
use Throwable;

class ApiError extends Exception
{
    protected $dontReport = [
        //
    ];

    public function __construct($message = "", $code = 0, Throwable $previous = NULL)
    {
        parent::__construct($message, $code, $previous);
    }

    public function render($request)
    {
        // 这里的 JSON 可以自定义 不一定要和博主一样
        return response()->json([
            'code' => $this->code,
            'msg' => $this->message
        ]);
    }
}


测试

创建路由(在 Web.php 路由写入路由群组 并使用中间件):

Route::middleware(['web_base'])->group(function () {
    Route::get('test', 'IndexController@test');
});


创建控制器

使用命令创建控制器:

php artisan make:controller IndexController


配置数据库

数据库配置信息在 .ENV 填写, 创建一个 users 表, 且结构如下:

1.png


创建模型

使用命令创建模型:

php artisan make:model Model/User

当然, 你可以使用 Laravel 自带的数据迁移并使用默认的 User 模型.


插入数据测试

在 IndexController 控制器写入 test 方法:

public function test()
{
    $model_user = new User();
    $model_user->name = 'name';
    $model_user->email = '123456@qq.com';
    $model_user->save();
}

此时访问 域名/test 可顺利插入数据:

2.png

修改 test 方法, 并故意留下语法错误:

public function test()
{
    $model_user = new User();
    $model_user->name = 'name';
    $model_user->email = '123456@qq.com';
    $model_user->save();

    dd(1)
}

再次访问 域名/test 刷新数据库可发现并没有插入数据. (此时并没有执行本文定义的事务 可能是 Laravel 的处理没有提交数据)

再次修改 test 方法:

public function test()
{
    $model_user = new User();

    $model_user->name = 'name';
    $model_user->email = 'email';
    $model_user->save();
    throw new ApiError('Something Went Wrong.', 111);   // 这里使用了自定义方法, 但并不规范 因为这个自定义错误是接口的 博主只是演示
}

测试访问, 会回滚数据. 浏览器也会响应返回自定义的错误信息.


删除测试

首先是正常删除:

public function test()
{
    $model_user = new User();
    $model_user->where('id', 1)->delete();
}

此时是正常删除的, 而修改成这样并抛出自定义错误则会回滚事务(未删除):

public function test()
{
    $model_user = new User();
    $model_user->where('id', 3)->delete();
    throw new ApiError('Something Went Wrong.', 111);
}


修改测试

正常修改:

public function test()
{
    $model_user = new User();
    $model_user->where('id', 3)->update(['name' => 1, 'email' => 1]);
}

此时是正常修改的, 抛出自定义错误时会回滚(未修改):

public function test()
{
    $model_user = new User();
    $model_user->where('id', 3)->update(['name' => 2, 'email' => 2]);
    throw new ApiError('Something Went Wrong.', 111);
}


有趣的地方

出现语法错误时, 例如随便写一个不存在的方法 或者 dd() 没有写结束的 ";" Laravel 都不会提交数据, 可以说明 Laravel 是在最后才提交数据到数据库的. 相当于 Laravel 本身就有一个后置中间件用于提交数据, 增加了数据安全. (同时测试了一下 ThinkPHP6 也是有同样的操作).


但 ThinkPHP6 和 Laravel 不一样的地方是, 同样的方法:

public function test()
{
    // $model_user = new User();       // Laravel   这个只是类名不一样
    // $model_user = new Users();      // ThinkPHP6 这个只是类名不一样
    $model_user->name = 'name';
    $model_user->email = 'email';
    $model_user->save();
    dd();
}

Laravel 并不会提交数据到数据库, 因为 dd 方法结束了运行, 而 TP6 会继续往数据库里面插入数据.

各有优缺点吧...


注意

数据库引擎务必为 InnoDB. 否则事务无效.


相关链接

博主写过一些相关的自定义信息 及 事务的文章:

Laravel 自定义接口错误响应

ThinkPHP6全局事务(中间件及订阅事件)

本文地址 : www.bubaijun.com/page.php?id=197

版权声明 : 未经允许禁止转载!

评论:我要评论
发布评论:
Copyright © 不败君 粤ICP备18102917号-1

不败君

首 页 作 品 微 语