目标:
上篇文章我们构建了一个基础的 api 接口,本篇文章我们要构建自己的异常处理以及 restful 返回。
抛出异常
我们可以在控制器中主动抛出一个异常,像这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route;
class Word {
public function index() { throw new \Exception('something was wrong!'); } }
|
你可以用curl命令来(或者用postman)请求[yourdomain]/hello/word查看效果。
异常捕获
上面我们已经抛出了异常,接下来我们需要捕获这个异常并进行处理。
在symfony中,有这么一个组件:
HttpKernel 组件提供了一个接口,该接口规范了从请求开始并创建适当响应的过程. 无论该系统的架构多么多样,该组件都是任何应用程序或框架的核心。
这个组件对外暴露了一个事件:kernel.exception,可以让我们很轻松的实现自定义异常处理
在config/services.yaml下加入以下代码:
1 2 3 4
| App\EventListener\Exception: tags: - { name: kernel.event_listener, event: kernel.exception }
|
其中,App\EventListener\Exception是我们的异常处理类,通过tags来告知symfony他是kernel.exception事件的监听者。
下面我们来实现App\EventListener\Exception类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <?php namespace App\EventListener;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\Event\ExceptionEvent;
class Exception {
public function onKernelException(ExceptionEvent $event) { $event->setResponse($this->createJsonRespons($event));
return $event; }
private function createJsonResponse (ExceptionEvent $event) { return JsonResponse::create([ 'message' => $event->getException()->getMessage(), 'requestUrl' => $event->getRequest()->getPathInfo() ], $event->getException()->getStatusCode()); } }
|
上面的代码中,我们捕获了抛出的异常,并且把异常信息以 json 数据形式返回。
自定义异常处理
上面的代码中我们能够通过onKernelException方法,能够捕获到异常,但是我们并不知道是框架自己的异常还是我们主动抛出的异常。为了区分以及构建更友好的restful结构,我们需要自定义异常。
我们在src目录下创建Exception\Base作为我们异常的基类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| <?php namespace App\Exception;
use Symfony\Component\HttpKernel\Exception\HttpException;
class Base extends HttpException {
protected $status = 500;
protected $message = 'unknown';
protected $errorCode = 999;
protected $data = [];
protected $accessKey = ['data', 'errorCode', 'message'];
public function __construct (array $errorData = [], int $statusCode = 0, \Throwable $previous = null) { foreach ($this->accessKey as $key => $value) { if (array_key_exists($value, $errorData)) $this->$value = $errorData[$value]; }
if ($statusCode) $this->setStatus($statusCode);
parent::__construct($this->getStatus(), $this->getMessage(), $previous, [], $this->getStatus()); }
...getter or setter }
|
以上的代码中,核心的代码即为构造方法。
结合我们的App\EventListener\Exception,我们把他的代码改为如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| <?php namespace App\EventListener;
use App\Exception\Base; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\Event\ExceptionEvent;
class Exception {
private $event;
private $statusCode = 500;
private $errorCode = 999;
private $message = '服务器异常';
private $data = [];
public function onKernelException(ExceptionEvent $event) { $this->setEvent($event); $exception = $event->getException(); if ($exception instanceof Base) { $this->setData($exception->getData()); $this->setMessage($exception->getMessage()); $this->setErrorCode($exception->getErrorCode()); $this->setStatusCode($exception->getStatus());
$event->setResponse($this->createJsonResponse()); }
return $event; }
private function createJsonResponse () { return JsonResponse::create([ 'message' => $this->getMessage(), 'errorCode' => $this->getErrorCode(), 'data' => $this->getData(), 'requestUrl' => $this->getEvent()->getRequest()->getPathInfo() ], $this->getStatusCode()); }
...getter or setter }
|
以上的代码我们捕获了所有的异常,并且如果是继承自App\Exception\Base异常的话能够很有好的返回 json 数据,其中包括了
status-http 状态码
message-异常消息
errorCode-自定义错误码
data-返回数据
requestUrl-当前请求接口
能够应付大部分场景,如果你觉得还不够的话,可以补充。
使用异常
我们已经有了自定义的异常,我们怎么来使用它呢?
我们首先定义一个参数异常类App\Exception\Parameter:
1 2 3 4 5 6 7 8 9
| <?php namespace App\Exception;
class Parameter extends Base { protected $status = 400; protected $errorCode = 10000; protected $message = "参数错误"; }
|
什么,才10行代码?没错,就是如此简单。
然后我们可以在控制器使用它了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php
namespace App\Controller;
use App\Exception\Parameter; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route;
class Word {
public function index() { throw new \Exception('something was wrong!'); }
public function custom() { throw new Parameter(); } }
|
为了避免重复,我们定义了另外一个路由hello\word,只不过请求方式换了POST而已。
产品环境下的 500 异常
如上,你已经自定义了异常处理了,但是细心的同学可能发现了,我们的抛出Exception的时候,还是是由框架渲染的,在产品环境下,我们不希望用户看到这些异常。
所以,我们只需要在产品环境下让框架的异常正常返回就可以了。为此我们需要拿到配置环境的值。
在config/services.yaml下加入以下代码:
1 2 3 4 5 6
| App\EventListener\Exception: tags: - { name: kernel.event_listener, event: kernel.exception } + arguments: + $debug: "%kernel.debug%"
|
这样我们拿到了配置debug的值。异常处理就容易了。
至此,完整的App\EventListener\Exception类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| <?php namespace App\EventListener;
use App\Exception\Base; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\Event\ExceptionEvent;
class Exception {
private $debug;
private $event;
private $statusCode = 500;
private $errorCode = 999;
private $message = '服务器异常';
private $data = [];
public function __construct ($debug) { $this->debug = $debug; }
public function onKernelException(ExceptionEvent $event) { $this->setEvent($event); $exception = $event->getException(); if ($exception instanceof Base) { $this->setData($exception->getData()); $this->setMessage($exception->getMessage()); $this->setErrorCode($exception->getErrorCode()); $this->setStatusCode($exception->getStatus());
$event->setResponse($this->createJsonResponse()); return $event; }
if ($this->debug) { return $event; }
$event->setResponse($this->createJsonResponse()); return $event; }
private function createJsonResponse () { return JsonResponse::create([ 'message' => $this->getMessage(), 'errorCode' => $this->getErrorCode(), 'data' => $this->getData(), 'requestUrl' => $this->getEvent()->getRequest()->getPathInfo() ], $this->getStatusCode()); } }
|
如上,我们已经完成了最复杂的一层构建。接下来我们来实现数据的校验层吧。
enjoy and happy coding!