LoginSignup
28
25

More than 5 years have passed since last update.

Laravelのレスポンス拡張 (正常/異常/メンテ)

Last updated at Posted at 2018-07-10

概要

Laravelでレスポンスを統一したかったので、Responseクラスの拡張方法を調べた結果をまとめます。

レスポンスマクロ

調べてみたらマニュアルに記述があったのでそのままやってみる。

ServiceProviderの作成

下記のようにServiceProviderのクラスを作成してみる。

app/Providers/ResponseApiServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Response;

/**
 * レスポンス (API)
 * @package App\Providers
 */
class ResponseApiServiceProvider extends ServiceProvider
{
    /**
     * アプリケーションのレスポンスマクロ登録
     *
     * @return void
     */
    public function boot()
    {
        // Success (200 OK.)
        Response::macro('success', function ($data) {
            return response()->json([
                'response' => $data
            ]);
        });

        // Error (4xx, 5xx)
        Response::macro('error', function ($message, array $errors = [], $status = ResponseStatus::HTTP_INTERNAL_SERVER_ERROR) {
            return response()->json([
                'message' => $message,
                'errors'  => (object) $errors
            ], $status);
        });

        // Under Maintenance (503 Service Unavailable.)
        Response::macro('maintenance', function () {
            return response()->json([
                'message' => 'Site is under maintenance.',
                'errors'  => (object) []
            ], ResponseStatus::HTTP_SERVICE_UNAVAILABLE);
        });
    }
}

作っただけだとだめなので、ServiceProviderを登録する。

app/config/app.php
<?php

return [

    ...

    'providers' => [
        /*
         * Application Service Providers...
         */

        ...

        App\Providers\RouteServiceProvider::class,
        App\Providers\ResponseApiServiceProvider::class,   <- 追加

    ],

    ...
];

使ってみる

まず、正常系を実装してみる。

app/Http/Controllers/HogeController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Request;

/**
 * Hoge Class
 * @package App\Http\Controllers
 */
class HogeController extends Controller
{
    /**
     * 正常系
     *
     * @access  public
     * @param   Request $request
     * @return  \Illuminate\Http\JsonResponse
     */
    public function success(Request $request)
    {
        $id = $request->input('id', '');
        if (empty($id)) {
            throw ValidationException::withMessages([
                'id' => 'id is required.',
            ]);
        }

        if ($id == '999') {
            throw new NotFoundHttpException('id is not exist.');
        }

        return response()->success([
            'email' => 'hoge@example.com'
        ]);
    }
}

次に異常系を実装してみる。

app/Exceptions/Handler.php
<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Response;
use Illuminate\Validation\ValidationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Exceptions\HttpResponseException;
use App\Providers\ResponseApiServiceProvider;

/**
 * 例外ハンドラー
 *
 * @package App\Exceptions
 */
class Handler extends ExceptionHandler
{
    /**
     * Report or log an exception.
     *
     * @param  \Exception $exception
     * @return void
     */
    public function report(Exception $exception)
    {
        parent::report($exception);
    }

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Exception $e
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function render($request, Exception $e)
    {
        // [お知らせ] レスポンス形式を統一するため、コメントアウトしました
        #if (method_exists($e, 'render') && $response = $e->render($request)) {
        #    return Router::toResponse($request, $response);
        #} elseif ($e instanceof Responsable) {
        #    return $e->toResponse($request);
        #}

        $e = $this->prepareException($e);
        if ($e instanceof HttpResponseException) {
            return $e->getResponse();
        } elseif ($e instanceof AuthenticationException) {
            $status  = Response::HTTP_UNAUTHORIZED;
            $message = Response::$statusTexts[$status];
            $errors  = [];
        } elseif ($e instanceof ValidationException) {
            // [お知らせ] レスポンス形式を統一するため、コメントアウトしました
            #if ($e->response) {
            #    return $e->response;
            #}

            $status  = $e->status;
            $message = Response::$statusTexts[$status];
            $errors  = $e->errors();
        } elseif ($this->isHttpException($e)) {
            $status  = $e->getStatusCode();
            $message = (isset(Response::$statusTexts[$status])) ? Response::$statusTexts[$status] : '';
            $errors  = [];
        } else {
            $status  = Response::HTTP_INTERNAL_SERVER_ERROR;
            $message = 'Server Error';
            $errors  = [];
        }

        // ResponseApiServiceProviderが実行される前にエラーが発生した場合の対応
        if (! method_exists(response(), 'error')) {
            $app = app();
            $provide = new ResponseApiServiceProvider($app);
            $provide->boot();
        }

        return response()->error($message, $errors, $status);
    }
}

メンテナンス中を実装してみる。

App\Http\Middleware\UnderMaintenance
<?php

namespace App\Http\Middleware;

use Closure;

class UnderMaintenance
{
    /**
     * メンテナンスチェック
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (env('APP_MAINTENANCE', false) === true) {
            // 503 Service Unavailable.
            return response()->maintenance();
        }

        return $next($request);
    }
}

すべての処理で上記を実行するので、Kernelに登録する。

App\Http\Kernel.php
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [

        ...

        \App\Http\Middleware\UnderMaintenance::class, // <- ここに追加
    ];

.envにフラグを追加

.env
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:*********************************
APP_DEBUG=true
APP_LOG_LEVEL=debug
APP_URL=http://localhost
APP_MAINTENANCE=false                                  <- ここに追加

以上

レスポンスについて

成功時は、下記のようなフォーマットのレスポンスが返ります。

成功(200_OK)
{
  "response": {
    "email": "hoge@example.com"
  }
}

エラー時は、下記のようなフォーマットのレスポンスが返ります。

エラー(403_Forbidden.)
{
    "message":"Forbidden",
    "errors":{}
}
エラー(404_not_found.)
{
    "message" : "Not Found"
    "errors" : { }
}
エラー(422_Unprocessable_Entity.)
{
  "message": "Unprocessable Entity"
  "errors": {
    "id": [
      "id is required."
    ]
  }
}

メンテナンス中の場合、下記フォーマットのレスポンスが返ります。

エラー(503_Site_is_under_maintenance.)
{
    "message": "Site is under maintenance.",
    "errors": {}
}

以上

修正履歴

  • 07/12 DB接続できなかった際の例外処理時にException/Handler.phpでエラー発生するので修正。
2018-07-12 03:40:27     local   ERROR   Database [localhost] not configured.    {"exception":"[object] (InvalidArgumentException(code: 0): Database [localhost] not configured. at /var/www/www.example.com/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php:140)"}        {"file":"/var/www/www.example.com/app/Exceptions/Handler.php","line":34,"class":"App\\Exceptions\\Handler","function":"report"}
2018-07-12 03:40:27     local   ERROR   Method error does not exist.    []      {"file":"/var/www/www.example.com/app/Exceptions/Handler.php","line":32,"class":"App\\Exceptions\\Handler","function":"report"}
2018-07-12 03:40:27     local   ERROR   Method error does not exist.    {"exception":"[object] (BadMethodCallException(code: 0): Method error does not exist. at /var/www/www.example.com/vendor/laravel/framework/src/Illuminate/Support/Traits/Macroable.php:96)"}   {"file":"/var/www/www.example.com/app/Exceptions/Handler.php","line":34,"class":"App\\Exceptions\\Handler","function":"report"}
  • 07/13 メンテンス中のレスポンスを追加

参考サイト

28
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
25