LaravelでAPIを作る際のエラーハンドリングメモ

めちゃくちゃに悩んでいるので頭の整理を兼ねてメモする。

経緯

現在アサインしている案件でLaravelを使用してAPIを作成することになっており、
機能実装に関してはさておき、エラーハンドリングをどうしようかという壁に当たった。

やったこと

Exceptionを定義してHttpExceptionをAPI用のエラーに入れ替える

まずはErrorHandler(app/Exceptions/Handler.php)の中身をいじり
404などのエラーでもJsonResponseを返すようにする。

<?php
    public function render($request, \Exception $exception)
    {
        // レスポンスをJSON形式に変更
        if ($request->is('api/*')) {
            $status = $this->isHttpException($exception) ? $exception->getStatusCode() : 500;
            return new JsonResponse([
                'message' => $exception->getMessage(),
            ], $status);
        }

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

しかし、このままだとgetMessageに何も入ってこないので、
API用のエラーメッセージを用意するかExceptionを用意するかしないといけない気がしている自分。
ここで書いているときにそういうことだよな(?)って思った。
ダックタイピング大事だな〜〜〜。

ということで、HttpExceptionをApi用のExceptionに置き換えるメソッドを作成した。
Api用のExceptionは基底抽象クラスのApiExceptionを継承している。

<?php
    public function render($request, \Exception $exception)
    {
        // レスポンスをJSON形式に変更
        if ($request->is('api/*')) {
            $e = $this->prepareApiException($exception);
            return new JsonResponse($e->toArray(), $e->getStatusCode());
        }

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

    /**
     * API用のExceptionに変換する
     *
     * @param Exception $exception
     * @return ApiException
     */
    protected function prepareApiException(Exception $exception)
    {
        switch (true) {
            case $exception instanceof ApiException:
                return $exception;
            case $exception instanceof AuthenticationException:
                return new UnauthorizedApiException('', $exception->getPrevious());
            case $exception instanceof MethodNotAllowedHttpException:
                return new MethodNotAllowedApiException('', $exception);
            case $exception instanceof ModelNotFoundException:
            case $exception instanceof NotFoundHttpException:
                return new NotFoundApiException('', $exception->getPrevious());
            case $exception instanceof ValidationException:
                return new ValidationFailedApiException($exception->errors(), '', $exception->getPrevious());
                break;
            default:
                return new InternalServerErrorApiException('', $exception->getPrevious());
        }
    }

Exceptionの中身はこんな感じ。

<?php

namespace App\Exceptions\Api;

use Exception;

abstract class ApiException extends Exception
{
    protected $headers = [];

    /**
     * @param int $statusCode
     * @param string $message
     * @param Exception $previous
     * @param array $headers
     */
    public function __construct(
        $statusCode = 0,
        $message = '',
        Exception $previous = null,
        array $headers = []
    ) {
        $this->headers = $headers;

        parent::__construct($message, $statusCode, $previous);
    }

    /**
     * Convert exception to array.
     *
     * @return array
     */
    public function toArray()
    {
        $return = [];
        $return['message'] = $this->getMessage();

        $errors = $this->getErrors();
        if (!empty($errors)) {
            $return['errors'] = $errors;
        }

        return $return;
    }

    /**
     * Add extra info to the output.
     *
     * @return array
     */
    public function getErrors(): array
    {
        return [];
    }
} 
<?php

namespace App\Exceptions\Api;

use Exception;

class ValidationFailedApiException extends ApiException
{
    /**
     * @var array
     */
    protected $errors = [];

    /**
     * Create a new ValidationFailedApiException.
     *
     * @param array $errors
     * @param string $message
     * @param Exception $previous
     */
    public function __construct(array $errors, $message = '', Exception $previous = null)
    {
        $this->errors = $errors;

        if (empty($message)) {
            $message = 'Validation failed.';
        }

        parent::__construct(422, $message, $previous);
    }

    /**
     * Get array of errors
     *
     * @return array
     */
    public function getErrors(): array
    {
        return $this->errors;
    }
} 

ここまでやって、
ここまでやる必要ある????
って思った僕。

f:id:matsup8:20200218025547p:plain

正直汎用的なエラーに関してはAPI用のエラーメッセージだけ用意しておいて、
独自エラーに関しては

return new JsonRespnse(['message' => 'hoge'], Response::HTTP_NOT_FOUND);  

とかなんかそんなふうにしてしまえば良いのではと思った。

f:id:matsup8:20200218025922p:plain

全然分かんね〜〜〜みんなどうやってるんだこれ〜〜〜〜〜〜

とりあえずここまで。