概要
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 メンテンス中のレスポンスを追加