본문 바로가기

Laravel

Laravel Responder

주의 : 영어 해석이 어색한 부분이 굉장히 많음.. 이상하다 싶으면 원문 읽길 바랍니다.

 

Laravel Responder는 API 응답을 구성하기 위한 패키지다.

transformers를 사용해서 데이터를 transform할 수 있고, 성공과 에러 응답을 생성하고 직렬화할 수 있다. 예외 처리를 하고 응답 테스트를 지원할 수 있다.

 

Intro

라라벨은 JSON으로 변환하기 위해 컨트롤러 메서드로부터 모델들을 바로 반환하게 한다. 이건 API를 구성하기 위해 빠른 방법이지만 DB column들이 드러나게 된다. Fractal은 transformers로 이 문제를 해결했다. 하지만 프레임워크와 통합하는 것은 좀 번거로울 수 있다.

public function index()
{
    // 사용자 모델들을 다 조회하고 UserTransformer 인스턴스에서 변형한 뒤 collection 인스턴스 생성
    $resource = new Collection(User::all(), new UserTransformer());
    
    직렬화(json encode)한 뒤 응답한다.
    return response()->json((new Manager)->createData($resource)->toArray());
}

responder를 써서 리팩토링을 하면 더 나아 보인다.

public function index()
{
    return responder()->success(User::all())->respond();
}

Requirements

PHP 7.0+

Laravel 5.1+ or Lumen 5.1+

 

Installation

composer require flugger/laravel-responder

Laravel

서비스 프로바이더에 등록

config/app.php 파일 내에 providers를 찾아서 다음 패키지를 등록해야 한다.

Flugg\Responder\ResponderServiceProvider::class,

라라벨 5.5 이상이면 서비스 프로바이더, 파사드가 자동 등록된다.

Usage

Responder 패키지는 Fractal이 어떻게 동작하는지 안다고 가정한다.

Creating Responses

패키지는 Responder 서비스 클래스를 갖는다. 각각 성공, 에러 응답을 구성하기 위한 success, error 메서드가 있다. 서비스를 사용하고 응답을 생성하기 위해 다음 옵션 중 하나를 골라야 한다.

 

Option 1. Inject Responder Service

Responder 서비스 클래스를 컨트롤러 메서드에 바로 inject할 수 있다.

public function index(Responder $responder)
{
    return $responder->success();
}

또한 에러 응답을 생성하기 위해 error 메서드를 사용할 수 있다. 

return $responder->error();

Option 2. responder 헬퍼

라라벨 response 헬퍼 함수의 열렬한 팬이라면, responder 헬퍼도 좋아할 수도 있다.

return responder()->success();
return responder()->error();

Option 3. Responder 파사드

return Responder::success();
return Responder::error();

Option 4. MakeResponses 계약

패키지는 컨트롤러에서 사용할 수 있는 Flugg\Responder\Http\MakesResponses trait를 제공한다.

return $this->success();
return $this->error();

옵션들은 다 동일하므로 선택하는 건 사용자의 몫, 일관되게 사용할 것. 2번째 옵션이 다음 설명들에 사용될 것이다.

Building Responses

success, error 메서드는 각각 SuccessResponseBuilder, ErrorResponseBuilder를 반환한다. 두 개 다 ResponseBuilder 추상 클래스의 구현체다. 컨트롤러로부터 반환될 때 JSON으로 변환될 것이다. 하지만 repond 메서드를 써서 Illuminate\Http\JsonResponse의 인스턴스를 명시적으로 생성할 수 있다.

return responder()->success()->respond();
return responder()->error()->respond();

상태 코드는 200이 기본이다. 그러나 첫번째 인수에 설정해서 바꿀 수 있다. 두번째 인수에 header 배열을 넘길 수 있다.

return responder()->success()->respond(201, ['x-foo' => true]);
return responder()->error()->respond(404, ['x-foo' => false]);

일관성을 유지하기 위해 항상 respond를 사용하는 것이 좋다.

Casting Response Data

응답을 json으로 변환하는 대신 respond 메서드를 써라. 응답 데이터를 몇가지 타입으로 바꿀 수 있다. toCollection, toJson 메서드도 가능하다.

return responder()->success()->toArray();

Decorating Response

A response decorator allows for last minute changes to the response before it's returned. The package comes with two response decorators out of the box adding a status and success field to the response output. The decorators key in the configuration file defines a list of all enabled response decorators:

'decorators' => [ \Flugg\Responder\Http\Responses\Decorators\StatusCodeDecorator::class, \Flugg\Responder\Http\Responses\Decorators\SuccessFlagDecorator::class, ],

You may disable a decorator by removing it from the list, or add your own decorator extending the abstract class Flugg\Responder\Http\Responses\Decorators\ResponseDecorator. You can also add additional decorators per response:

return responder()->success()->decorator(ExampleDecorator::class)->respond();

return responder()->error()->decorator(ExampleDecorator::class)->respond();


The package also ships with some situational decorators disabled by default, but which can be added to the decorator list:

  • PrettyPrintDecorator decorator will beautify the JSON output;

\Flugg\Responder\Http\Responses\Decorators\PrettyPrintDecorator::class,

  • EscapeHtmlDecorator decorator, based on the "sanitize input, escape output" concept, will escape HTML entities in all strings returned by your API. You can securely store input data "as is" (even malicious HTML tags) being sure that it will be outputted as un-harmful strings. Note that, using this decorator, printing data as text will result in the wrong representation and you must print it as HTML to retrieve the original value.

\Flugg\Responder\Http\Responses\Decorators\EscapeHtmlDecorator::class,

Creating Success Responses

성공 응답은 success 메서드를 사용해서 만들어진다.

return responder()->success()->respond();

위 코드는 다음과 같은 JSON 출력이 된다.

{
    "status": 200,
    "success": true,
    "data": null
}

Setting Response Data

success 메서드는 첫번째 인수로 응답 데이터를 가져간다.

return responder()->success(Product::all())->respond();

쿼리 빌더, 관계 인스턴스를 지원한다. responder 패키지는 쿼리를 실행하고 collection으로 변환한다. 내 생각에 이런 식으로 사용하는 경우는 거의 없을 것 같다.

Transforming Response Data

응답에 transformer를 붙이면 Fractal과 함께 응답 데이터가 변환될 것이다. transformer를 붙이는 데 두 가지 방법이 있다.

1. 응답에 명시적으로 설정하기 2. 모델에 묵시적으로 바인딩하기.

 

Setting Transformer On Response

success 메서드에 두번째 인수를 보내서 transformer를 응답에 붙일 수 있다. 예를 들어, 아래와 같이 간단한 closure 트랜스포머를 붙일 수 있다. 제품 목록을 변환해서 이름만 출력할 수 있다.

return responder()->success(Product::all(), function ($product) {
    return ['name' => $product->name];
})->respond();

또한 트랜스포머 클래스를 사용해서 변환할 수 있다.

return responder()->success(Product::all(), ProductTransformer::class)->respond();
return responder()->success(Product::all(), new ProductTransformer)->respond();

Binding Transformer To Model

트랜스포머가 설정되지 않았다면, 패키지는 트랜스포머 form을 resolve하는 Flugg\Responder\Contracts\Transformable 인터페이스를 구현하는 요소를 위해 응답 데이터를 찾을 것이다. Transformable 인터페이스를 모델 안에서 구현함으로써 사용할 수 있다.

class Product extends Model implements Transformable {}

상응하는 트랜스포머를 반환하는 transformer 메서드를 추가함으로써 제약을 충족시킬 수 있다.

/**
 * Get a transformer for the class.
 *
 * @return \Flugg\Responder\Transformers\Transformer|string|callable
 */
public function transformer()
{
    return ProductTransformer::class;
}

클래스 이름 문자열을 반환하는 것에 국한되지 않고, 트랜스포머 인스턴스 또는 success 메서드의 두번째 인수처럼 closure 트랜스포머를 반환할 수 있다.

 

모든 모델들에 대해 Transformable을 구현하는 대신, 대안적인 접근은 트랜스포머들을 bind 메서드를 써서 TransformerResolver 클래스에 바인딩하는 것이다. AppServiceProvier 내에 코드를 작성하거나 완전히 새로운 TransformerServiceProvider를 생성해 작성할 수 있다.

// TransformerResolver import
use Flugg\Responder\Contracts\Transformers\TransformerResolver;

public function boot()
{
    $this->app->make(TransformerResolver::class)->bind([
        \App\Product::class => \App\Transformers\ProductTransformer::class,
        \App\Shipment::class => \App\Transformers\ShipmentTransformer::class,
    ]);
}

모델에 트랜스포머를 바인드한 뒤에 두번째 인수를 생략하고 데이터를 변형할 수 있다.

return responder()->success(Product::all())->respond();

Setting Resource Key

만약 응답에 보낸 데이터가 모델이거나 모델 목록을 포함한다면, resource key는 묵시적으로 모델의 테이블 이름으로부터 resolve될 것이다. getResourceKey 메서드를 모델에 추가함으로써 덮어쓸 수 있다.

public function getResourceKey(): string {
    return 'products';
}

또한 명시적으로 success 메서드의 3번째 인수에 보냄으로써 응답에 resource key를 설정할 수 있다.

return responder()->success(Product::all(), ProductTransformer::class, 'products')->respond();

Paginating Response Data

Sending a paginator to the success method will set pagination meta data and transform the data automatically, as well as append any query string parameters to the paginator links.

return responder()->success(Product::paginate())->respond();

Assuming there are no products and the default configuration is used, the JSON output would look like:

{ "success": true, "status": 200, "data": [], "pagination": { "total": 0, "count": 0, "perPage": 15, "currentPage": 1, "totalPages": 1, "links": [] } }

Setting Paginator On Response

Instead of sending a paginator as data, you may set the data and paginator seperately, like you traditionally would with Fractal. You can manually set a paginator using the paginator method, which expects an instance of League\Fractal\Pagination\IlluminatePaginatorAdapter:

$paginator = Product::paginate(); $adapter = new IlluminatePaginatorAdapter($paginator); return responder()->success($paginator->getCollection())->paginator($adapter)->respond();

Setting Cursor On Response

You can also set cursors using the cursor method, expecting an instance of League\Fractal\Pagination\Cursor:

if ($request->has('cursor')) { $products = Product::where('id', '>', request()->cursor)->take(request()->limit)->get(); } else { $products = Product::take(request()->limit)->get(); } $cursor = new Cursor(request()->cursor, request()->previous, $products->last()->id ?? null, Product::count()); return responder()->success($products)->cursor($cursor)->respond();

Including Relationships

If a transformer class is attached to the response, you can include relationships using the with method:

return responder()->success(Product::all())->with('shipments')->respond();

You can send multiple arguments and specify nested relations using dot notation:

return responder()->success(Product::all())->with('shipments', 'orders.customer')->respond();

All relationships will be automatically eager loaded, and just like you would when using with or load to eager load with Eloquent, you may use a callback to specify additional query constraints. Like in the example below, where we're only including related shipments that hasn't yet been shipped:

return responder()->success(Product::all())->with(['shipments' => function ($query) { $query->whereNull('shipped_at'); }])->respond();

Including From Query String

Relationships are loaded from a query string parameter if the load_relations_parameter configuration key is set to a string. By default, it's set to with, allowing you to automatically include relations from the query string:

GET /products?with=shipments,orders.customer Excluding Default Relations

In your transformer classes, you may specify relations to automatically load. You may disable any of these relations using the without method:

return responder()->success(Product::all())->without('comments')->respond();

Filtering Transformed Data

The technique of filtering the transformed data to only return what we need is called sparse fieldsets and can be specified using the only method:

return responder()->success(Product::all())->only('id', 'name')->respond();

When including relationships, you may also want to filter fields on related resources as well. This can be done by instead specifying an array where each key represents the resource keys for the resources being filtered

return responder()->success(Product::all())->with('shipments')->only([ 'products' => ['id', 'name'], 'shipments' => ['id'] ])->respond();

Filtering From Query String

Fields will automatically be filtered if the filter_fields_parameter configuration key is set to a string. It defaults to only, allowing you to filter fields from the query string:

GET /products?only=id,name

You may automatically filter related resources by setting the parameter to a key-based array:

GET /products?with=shipments&only[products]=id,name&only[shipments]=id

Adding Meta Data

You may want to attach additional meta data to your response. You can do this using the meta method:

return responder()->success(Product::all())->meta(['count' => Product::count()])->respond();

When using the default serializer, the meta data will simply be appended to the response array:

{ "success": true, "status": 200, "data": [], "count": 0 }

Serializing Response Data

After the data has been transformed, it will be serialized using the specified success serializer in the configuration file, which defaults to the package's own Flugg\Responder\Serializers\SuccessSerializer. You can overwrite this on your responses using the serializer method:

return responder()->success()->serializer(JsonApiSerializer::class)->respond();

return responder()->success()->serializer(new JsonApiSerializer())->respond();

Above we're using Fractal's JsonApiSerializer class. Fractal also ships with an ArraySerializer and DataArraySerializer class. If none of these suit your taste, feel free to create your own serializer by extending League\Fractal\Serializer\SerializerAbstract. You can read more about it in Fractal's documentation.

Creating Transformers

A dedicated transformer class gives you a convenient location to transform data and allows you to reuse the transformer at multiple places. It also allows you to include and transform relationships. You can create a transformer using the make:transformer Artisan command:

php artisan make:transformer ProductTransformer

The command will generate a new ProductTransformer.php file in the app/Transformers folder:

<?php namespace App\Transformers; use App\User; use Flugg\Responder\Transformers\Transformer; class ProductTransformer extends Transformer { /** * List of available relations. * * @var string[] */ protected $relations = []; /** * A list of autoloaded default relations. * * @var array */ protected $load = []; /** * Transform the model. * * @param \App\Product $product * @return array */ public function transform(Product $product): array { return [ 'id' => (int) $product->id, ]; } }

It will automatically resolve a model name from the name provided. For instance, the package will extract Product from ProductTransformer and assume the models live directly in the app folder (as per Laravel's convention). If you store them somewhere else, you can use the --model (or -m) option to override it:

php artisan make:transformer ProductTransformer --model="App\Models\Product"

Creating Plain Transformers

The transformer file generated above is a model transformer expecting an App\Product model for the transform method. However, we can create a plain transformer by applying the --plain (or -p) modifier:

php artisan make:transformer ProductTransformer --plain

This will remove the typehint from the transform method and add less boilerplate code.

Setting Relationships

The $relations and $load properties in the transformer are the equivalent to Fractal's own $availableIncludes and $defaultIncludes. In addition to the slight name change, the package uses the $relations and $load properties to map out all available relationships for eager loading, so in contrast to Fractal, you should map the relationship to their corresponding transformer:

protected $relations = [ 'shipments' => ShipmentTransformer::class, ];


You can choose to skip the mapping and just pass the strings like with Fractal, but that means the package wont be able to eager load relationships automatically.


Setting Whitelisted Relationships

The $relations property specifies a list of relations available to be included. You can set a list of relations mapped to their corresponding transformers:

protected $relations = [ 'shipments' => ShipmentTransformer::class, 'orders' => OrderTransformer::class, ];

Setting Autoloaded Relationships

The $load property specifies a list of relations to be autoloaded every time you transform data with the transformer:

protected $load = [ 'shipments' => ShipmentTransformer::class, 'orders' => OrderTransformer::class, ];


You don't have to add relations to both $relations and $load, all relations in $load will be available by nature.


Including Relationships

While Fractal requires you to to create a method in your transformer for every included relation, this package lets you skip this when transforming models, as it will automatically fetch relationships from the model. You may of course override this functionality by creating an "include" method:

/** * Include related shipments. * * @param \App\Product $product * @return mixed */ public function includeShipments(Product $product) { return $product->shipments; }

Unlike Fractal you can just return the data directly without wrapping it in an item or collection method.


You should be careful with executing database calls inside the include methods as you might end up with an unexpected amount of hits to the database.


Using Include Parameters

Fractal can parse query string parameters which can be used when including relations. For more information about how to format the parameters see Fractal's documentation on parameters. You may access the parameters by adding a second parameter to the "include" method:

public function includeShipments(Product $product, Collection $parameters) { return $product->shipments->take($parameters->get('limit')); }


To be as decoupled from Fractal as possible the parameters (which are normally accessed using League\Fractal\ParamBag) are accessed as Laravel collections instead.


Adding Query Constraints

Just as you can specify a query constraint when including a relationship with the with method, you can also add query constraints as a "load" method on the transformer. This will automatically be applied when extracting relationships for eager loading.

/** * Load shipments with constraints. * * @param \Illuminate\Database\Eloquent\Builder $query * @return \Illuminate\Database\Eloquent\Builder */ public function loadShipments($query) { return $query->whereNull('shipped_at'); }


Note: You cannot mix "include" and "load" methods because the package doesn't eager load relationships included with an "include" method.


Filtering Relationships

After a relation has been included, you can make any last second changes to it using a filter method. For instance, below we're filtering the list of related shipments to only include shipments that has not been shipped:

/** * Filter included shipments. * * @param \Illuminate\Database\Eloquent\Collection $shipments * @return \Illuminate\Support\Collection */ public function filterShipments($shipments) { return $shipments->filter(function ($shipment) { return is_null($shipment->shipped_at); }); }

Transforming Data

We've looked at how to transform response data of success responses, however, there may be other places than your controllers where you want to transform data. An example is broadcasted events where you're exposing data using websockets instead of HTTP. You just want to return the transformed data, not an entire response.

It's possible to simply transform data by newing up the transformer and calling transform:

return (new ProductTransformer)->transform(Product::first());

However, this approach might become a bit messy when building transformations with relationships:

return array_merge((new ProductTransformer)->transform($product = Product::first()), [ 'shipments' => $product->shipments->map(function ($shipment) { return (new ShipmentTransformer)->transform($shipment); }) ]);

Yuck! Imagine that with multiple nested relationships. Let's explore a simpler way to handle this.

Building Transformations

The SuccessResponseBuilder actually delegates all of the transformation work to a dedicated Flugg\Responder\TransformBuilder class. We can use this class ourself to transform data. For instance, if the product and shipment transformers were bound to the models, we could replicate the code above in the following way:

public function index(TransformBuilder $transformation) { return $transformation->resource(Product::all())->with('shipments')->transform(); }

Instead of using the success method on the Responder service, we use the resource method on the TransformBuilder with the same method signature. We also use transform to execute the transformation instead of respond as we did when creating responses. In addition to the with method, you also have access to the other transformation methods like without, only, meta and serializer.


Using toArray on the Responder service is almost the same as the code above, however, it will also include response decorators which might not be desired.


Transforming Without Serializing

When using the TransformBuilder to transform data it will still serialize the data using the configured serializer. Fractal requires the use of a serializer to transform data, but sometimes we're just interested in the raw transformed data. The package ships with a Flugg\Responder\Serializers\NoopSerializer to solve this, a no-op serializer which leaves the transformed data untouched:

return $transformation->resource(Product::all())->serializer(NoopSerializer::class)->transform();

If you think this is looking messy, don't worry, there's a quicker way. In fact, you will probably never even need to utilize the NoopSerializer or TransformBuilder manually, but it helps to know how it works. The Flugg\Responder\Transformation is a class which can be used for quickly transforming data without serializing.

Option 1: The Transformation Service

The Transformation class utilizes the TransformBuilder class to build a transformation using the NoopSerializer. You can inject the Transformation class and call make to obtain an instance of TransformBuilder which gives you access to all of the chainable methods including with, like below:

public function __construct(Transformation $transformation) { $transformation->make(Product::all())->with('shipments')->transform(); }

Option 2: The transformation Helper

You can use the transformation helper function to transform data without serializing:

transformation(Product::all())->with('shipments')->transform();

Option 3: The Transformation Facade

You can also use the Transformation facade to achieve the same thing:

Transformation::make(Product::all())->with('shipments')->transform();

Transforming To Camel Case

Model attributes are traditionally specified in snake case, however, you might prefer to use camel case for the response fields. A transformer makes for a perfect location to convert the fields, as seen from the soldOut field in the example below:

return responder()->success(Product::all(), function ($product) { return ['soldOut' => (bool) $product->sold_out]; })->respond();

Transforming Request Parameters

After responding with camel case, you probably want to let people send in request data using camel cased parameters as well. The package provides a Flugg\Responder\Http\Middleware\ConvertToSnakeCase middleware you can append to the $middleware array in app/Http/Kernel.php to convert all parameters to snake case automatically:

protected $middleware = [ // ... \Flugg\Responder\Http\Middleware\ConvertToSnakeCase::class, ];


The middleware will run before request validation, so you should specify your validation rules in snake case as well.

Creating Error Responses

API 사용자가 예상치 못한 뭔가를 할 때 문제를 묘사하는 에러 응답을 반환할 수 있다. 에러 응답은 error 메서드를 써서 생성될 수 있다.

return responder()->error()->respond();

에러 응답은 에러 코드에 대한 지식과 상응하는 에러 메시지와 선택적으로 몇몇 에러 데이터를 가진다. 기본 설정에선, 위 코드는 다음과 같은 JSON 출력이 될 수 있다.

{
    "success": false,
    "status": 500,
    "error": {
        "code": null,
        "message": null
    }
}

Setting Error Code & Message

error 메서드의 첫번째 인수를 에러 코드를 설정해서 채울 수 있다.

return responder()->error('sold_out_error')->respond();

에러 코드에 정수를 사용할 수도 있다.

추가적으로 두번째 인수에 에러를 묘사하는 에러 메시지를 설정할 수 있다.

return responder()->error('sold_out_error', 'The requested product is sold out.')->respond();

Set Messages In Language Files

You can set the error messages in a language file, which allows for returning messages in different languages. The configuration file has an error_message_files key defining a list of language files with error messages. By default, it is set to ['errors'], meaning it will look for an errors.php file inside resources/lang/en. You can use these files to map error codes to corresponding error messages:

return [ 'sold_out_error' => 'The requested product is sold out.', ];

Register Messages Using ErrorMessageResolver

Instead of using language files, you may alternatively set error messages directly on the ErrorMessageResolver class. You can place the code below within AppServiceProvider or an entirely new TransformerServiceProvider:

use Flugg\Responder\ErrorMessageResolver; public function boot() { $this->app->make(ErrorMessageResolver::class)->register([ 'sold_out_error' => 'The requested product is sold out.', ]); }

Adding Error Data

에러 응답에 추가적인 데이터를 설정하고 싶을 수도 있다. 아래 예제와 같이, shipments의 목록을 반환할 것이다. sold_out 에러 응답과 함께, 

return responder()->error('sold_out')->data(['shipments' => Shipment::all()])->respond();

에러 데이터는 응답 데이터에 추가될 것이다. 기존 시리얼라이저를 사용하고 있고 DB에 shipments 데이터가 없다고 가정하면, 위 코드는 아래와 같을 것이다.

{
    "success": false,
    "status": 500,
    "error": {
        "code": "sold_out",
        "message": "The requested product is sold out.",
        "shipments": []
    }
}

Serializing Response Data

성공 응답과 유사하게, 에러 응답은 설정 파일에 있는 특정 시리얼라이저를 사용해서 시리얼라이즈될 것이다. 패키지 자체의 Flugg\Responder\Serializers\ErrorSerializer가 기본이지만, serializer 메서드를 사용함으로써 바꿀 수 있다.

return responder()->error()->serializer(ExampleErrorSerializer::class)->respond();
return responder()->success()->serializer(new ExampleErrorSerializer())->respond();

Flugg\Responder\Contracts\ErrorSerializer 계약을 구현함으로써 자신만의 에러 시리얼라이저를 생성할 수 있다.

Handling Exceptions

에러를 피하려고 해도 예외는 생긴다. 예외를 우아한 방법으로 응답하는 것은 API의 유저 경험을 향상시킨다. 패키지는 예외를 에러 응답으로 자동적으로 바꿔서 예외 처리를 발전시킬 수 있다. 사용하고 싶다면, 패키지의 exception handler를 사용하거나 아래에 자세히 설명된 계약을 포함하면 된다.

 

option 1. Handler Class 대체

패키지의 예외 처리를 사용하려면 app/Exceptions/Handler.php에 있는 기본을 바꿔야 한다.

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

패키지의 핸들러 클래스

use Flugg\Responder\Exceptions\Handler as ExceptionHandler;

 

option 2. ConvertsExceptions 계약 사용

패키지의 예외 처리는 이 작업의 대부분을 적재하기 위해 Flugg\Responder\Exceptions\ConvertsExceptions 계약을 사용한다. 예외 처리를 대체하는 대신, 핸들러 클래스에 계약을 사용할 수 있다. 예외 처리의 행위를 복제하기 위해, render 메서드에 아래 코드를 추가해야만 한다.

public function render($request, Exception $exception)
{
    $this->convertDefaultException($exception);

    if ($exception instanceof HttpException) {
        return $this->renderResponse($exception);
    }

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

실제로 JSON 요청에 대해서만 JSON 응답을 반환하고 싶다면, 위 코드를 wantsJson 검사에서 감쌀 수 있다.

if ($request->wantsJson()) {
    $this->convertDefaultException($exception);

    if ($exception instanceof HttpException) {
        return $this->renderResponse($exception);
    }
}

Converting Exceptions

Once you've implemented one of the above options, the package will convert some of Laravel's exceptions to an exception extending Flugg\Responder\Exceptions\Http\HttpException. It will then convert these to an error response. The table below shows which Laravel exceptions are converted and what they are converted to. All the exceptions on the right is under the Flugg\Responder\Exceptions\Http namespace and extends Flugg\Responder\Exceptions\Http\HttpException. All exceptions extending the HttpException class will be automatically converted to an error response.

Caught ExceptionsConverted To

Illuminate\Auth\AuthenticationException UnauthenticatedException
Illuminate\Auth\Access\AuthorizationException UnauthorizedException
Symfony\Component\HttpKernel\Exception\NotFoundHttpException PageNotFoundException
Illuminate\Database\Eloquent\ModelNotFoundException PageNotFoundException
Illuminate\Database\Eloquent\RelationNotFoundException RelationNotFoundException
Illuminate\Validation\ValidationException ValidationFailedException

You can disable the conversions of some of the exceptions above using the $dontConvert property:

/** * A list of default exception types that should not be converted. * * @var array */ protected $dontConvert = [ ModelNotFoundException::class, ];


If you're using the trait option, you can disable all the default conversions by removing the call to convertDefaultException in the render method.


Convert Custom Exceptions

In addition to letting the package convert Laravel exceptions, you can also convert your own exceptions using the convert method in the render method:

$this->convert($exception, [ InvalidValueException => PageNotFoundException, ]);

You can optionally give it a closure that throws the new exception, if you want to give it constructor parameters:

$this->convert($exception, [ MaintenanceModeException => function ($exception) { throw new ServerDownException($exception->retryAfter); }, ]);

 

Creating HTTP Exceptions

예외 클래스는 에러에 관한 정보를 저장하기 편리한 장소다. 패키지는 상태 코드, 에러 코드, 에러 메시지에 대한 지식이 있는 Flugg\Responder\Exceptions\Http\HttpException 추상 예외 클래스를 제공한다. 위 예제를 계속해서 자신만의 HttpException 클래스를 생성할 수 있다.

<?php

namespace App\Exceptions;

use Flugg\Responder\Exceptions\Http\HttpException;

class SoldOutException extends HttpException
{
    /**
     * The HTTP status code.
     *
     * @var int
     */
    protected $status = 400;

    /**
     * The error code.
     *
     * @var string|null
     */
    protected $errorCode = 'sold_out_error';

    /**
     * The error message.
     *
     * @var string|null
     */
    protected $message = 'The requested product is sold out.';
}

추가적인 에러 데이터를 반환하는 data 메서드를 추가할 수 있다.

/**
 * Retrieve additional error data.
 *
 * @return array|null
 */
public function data()
{
    return [
        'shipments' => Shipment::all()
    ];
}

패키지가 예외 처리를 하게 하려면, 애플리케이션 어디에서나 예외를 발생시킬 수 있고 자동적으로 에러 응답에 렌더링될 것이다.

throw new SoldOutException();

 

 

https://github.com/flugger/laravel-responder

 

flugger/laravel-responder

A Laravel Fractal package for building API responses, giving you the power of Fractal with Laravel's elegancy. - flugger/laravel-responder

github.com