2020-06-19 18:12:00
围观(8830)
只要进行了多表操作, 都应该开启事务, 如果每次遇到需要多表操作的方法都手动开事务是很累的...
所以本文使用 TP6 的 中间件 + 订阅事件 + 模型事件 进行全局的事务.
安装
首先安装 TP6, 文档地址: https://www.kancloud.cn/manual/thinkphp6_0/1037481 直接看文档就能安装.
安装好了 TP6 可以玩玩 TP6 的多应用模式. 文档: https://www.kancloud.cn/manual/thinkphp6_0/1297876
使用 PHPStudy 配置好之后, 打开域名可以看到这样的页面:
控制器及多应用
在 app 下新建一个 api 目录, 当然这里并非只能创建 api 目录, 查看文档就知道是可以随意添加.
根据官方文档, 需要在 api 目录下新增几个目录: controller / model / view / config / route, 由于模型可能被多个应用共用, 所以可以不创建 model 而是在 app 目录下创建 model 目录.
在 api 目录下继续新建一个 base 用于存放自己设置的控制器基类, 可以将之前保留的 BaseController.php 复制到 api/base 下.
使用编辑器打开 BaseController.php 需要将里面的命名空间修改:
namespace app\api\base;
中间件
接着就是在这个控制器基类里面定义中间件, 所以继续在 api 新建一个目录: middleware
使用命令生成一个中间件:
php think make:middleware BaseResponse
此时这个中间件并没有在想要的地方(TP6 文档没写怎么生成在指定位置 有空博主再去看看), 可以手动移动过去.
移动过去后也一样要修改命名空间:
namespace app\api\middleware;
中间件的 handle 方法需要写入:
public function handle($request, Closure $next) { $response = $next($request); $this->code = $response->getCode(); return $response; }
中间件类需要添加一个属性:
protected $code = 0;
再回到控制器, 可以直接在控制器的 $middleware 里面写上刚创建好的中间件:
protected $middleware = [BaseResponse::class];
如果用的不是 PHPStorm 需要手动 use 一下中间件. 否则会报错类不存在.
事件订阅
事件订阅的官方文档: https://www.kancloud.cn/manual/thinkphp6_0/1037492
使用命令生成事件:
php think make:event DBSubmit
打开 app / event 的 DBSubmit.php
写入代码:
private $start = false; public function onTaskStart() { if (!$this->start) { $this->start = true; Db::startTrans(); } } public function onTaskSuccess() { if ($this->start) { Db::commit(); } } public function onTaskFail() { if ($this->start) { Db::rollback(); } } public function subscribe(Event $event) { $event->listen('dbStartTask', [$this,'onTaskStart']); $event->listen('dbCommit', [$this,'onTaskSuccess']); $event->listen('dbRollback',[$this,'onTaskFail']); }
需要 use 的:
use think\Event; use think\facade\Db;
再到 app 下的 event.php 定义一下事件订阅:
'subscribe' => [ 'app\event\DBSubmit', // 更多事件订阅 ],
回到中间件, 在 BaseResponse 中间件中添加一个 end 方法并写入监听:
public function end(\think\Response $response) { if ($this->code == 200) { // 提交数据 event('dbCommit'); } else { // 回滚数据 event('dbRollback'); } }
可以简单理解为, 中间件响应成功就是代码没抛异常(后续业务逻辑判断有问题时需要手动抛异常), 就可以提交数据.
模型
在 api / base 下创建一个 BaseModel.php 并写入:
<?php namespace app\api\base; use think\Model; class BaseModel extends Model { }
后面新建的模型都继承这个模型基类, 如果需要全局软删除就可以直接改写这个基类模型.
由于现在我们的目的是事务, 所以需要在这个基类模型中添加一些模型事件.
官方文档: https://www.kancloud.cn/manual/thinkphp6_0/1037598
主要就是在五个事件中添加一下开启事务.
新增前 onBeforeInsert, 更新前 onBeforeUpdate, 写入前 onBeforeWrite, 删除前 onBeforeDelete, 恢复前 onBeforeRestore.
写好之后的代码是这样的:
<?php namespace app\api\base; use think\Model; class BaseModel extends Model { public static function onBeforeWrite(Model $model) { self::targetDbTaskStart(); } public static function onBeforeUpdate(Model $model) { self::targetDbTaskStart(); $data = array_merge($model->getData(), [$model->updateTime => date('Y-m-d H:i:s')]); $model->data($data); } public static function onBeforeInsert(Model $model) { self::targetDbTaskStart(); $data = array_merge($model->getData(), [$model->createTime => date('Y-m-d H:i:s')]); $model->data($data); } public static function onBeforeRestore(Model $model) { self::targetDbTaskStart(); } public static function onBeforeDelete(Model $model) { self::targetDbTaskStart(); } private static function targetDbTaskStart() { event('dbStartTask'); } }
抛出异常
最后需要在抛出异常的类里面添加回滚 (app / ExceptionHandle.php).
public function render($request, Throwable $e): Response { event('dbRollback'); // 添加自定义异常处理机制 // 其他错误交给系统处理 return parent::render($request, $e); }
测试
需要先配置好数据库, TP6 和 Laravel 一样, 创建一个 .env 文件在根目录, 然后添加配置:
[database] hostname = '127.0.0.1'; database = 'test'; username = 'root'; password = 'root'; hostport = '3306';
在本地这个数据库 test 添加一个 test 表. 并创建这个表的模型. 别忘了要继承 BaseModel.
表结构:
在 api / controller 里面创建一个 Index.php 类, 记住要继承 BaseController 类, 否则中间件不起效.
在 Index 控制器写入 test 方法:
public function test() { $model_test = new Test(); $model_test->save([ 'name' => 1 ]); return $x; }
因为变量 x 不存在, 所以这里是会报错的. 报错之后刷新数据库发现数据没有增加.
手动抛异常
因为这个中间件的原因, 如果在控制器返回类似这样的数据:
return [ 'code' => 0, 'msg' => 'msg' ]
这样也是不会回滚的, 因为正常响应了中间件也提交了数据.
在 api 下创建一个 exception 目录. 在这个目录新建 ApiException.php 类 写入代码:
<?php namespace app\api\exception; use think\Response; use Throwable; class ApiException extends \think\Exception { public static function response($request, Throwable $e): Response { $msg = $e->getMessage(); $code = $e->getCode(); if (empty($code)) { $code = 999; } return json([ 'msg' => $msg, 'code' => $code ]); } }
写入一个助手函数(文件在 app / common.php):
use app\api\exception\ApiException; use think\Response; if (!function_exists('errorService')) { function errorService($msg): Response { throw new ApiException($msg); } }
app 下的 ExceptionHandle 类的 render 方法也要增加一个判断:
public function render($request, Throwable $e): Response { event('dbRollback'); // 添加自定义异常处理机制 if ($e instanceof ApiException) { return ApiException::response($request, $e); } // 其他错误交给系统处理 return parent::render($request, $e); }
在 Index 控制器中, 在方法里面使用 errorService('msg', int $code); 就能返回异常信息给前端. 同时会回滚数据.
public function test() { $model_test = new Test(); $model_test->save( [ 'name' => 1 ] ); errorService('msg', 222); }
把 errorService 方法注释 就可以正常存入数据.
事务如果启用正常, 注释 errorService 和 不注释 errorService 请求接口. 可以看到数据库的数据变化:
注意
数据表的引擎(类型)一定不能是 MyISAM 而是应该使用 InnoDB.
这是数据库的基本知识, 就不多说了.
本文地址 : bubaijun.com/page.php?id=189
版权声明 : 未经允许禁止转载!
上一篇文章: PHP采集运营类目微信商户类目
下一篇文章: Windows10观看无广告的芒果TV