├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer.json
├── config
└── responder.php
├── resources
├── lang
│ └── en
│ │ └── errors.php
└── stubs
│ ├── transformer.model.stub
│ └── transformer.plain.stub
└── src
├── Console
└── MakeTransformer.php
├── Contracts
├── ErrorFactory.php
├── ErrorMessageResolver.php
├── ErrorSerializer.php
├── Pagination
│ └── PaginatorFactory.php
├── Resources
│ ├── ResourceFactory.php
│ └── ResourceKeyResolver.php
├── Responder.php
├── ResponseFactory.php
├── SimpleTransformer.php
├── TransformFactory.php
├── Transformable.php
└── Transformers
│ └── TransformerResolver.php
├── ErrorFactory.php
├── ErrorMessageResolver.php
├── Exceptions
├── ConvertsExceptions.php
├── Handler.php
├── Http
│ ├── HttpException.php
│ ├── PageNotFoundException.php
│ ├── RelationNotFoundException.php
│ ├── UnauthenticatedException.php
│ ├── UnauthorizedException.php
│ └── ValidationFailedException.php
├── InvalidErrorSerializerException.php
├── InvalidSuccessSerializerException.php
└── InvalidTransformerException.php
├── Facades
├── Responder.php
└── Transformation.php
├── FractalTransformFactory.php
├── Http
├── MakesResponses.php
├── Middleware
│ └── ConvertToSnakeCase.php
└── Responses
│ ├── Decorators
│ ├── EscapeHtmlDecorator.php
│ ├── PrettyPrintDecorator.php
│ ├── ResponseDecorator.php
│ ├── StatusCodeDecorator.php
│ └── SuccessFlagDecorator.php
│ ├── ErrorResponseBuilder.php
│ ├── Factories
│ ├── LaravelResponseFactory.php
│ └── LumenResponseFactory.php
│ ├── ResponseBuilder.php
│ └── SuccessResponseBuilder.php
├── Pagination
├── CursorPaginator.php
└── PaginatorFactory.php
├── Resources
├── DataNormalizer.php
├── ResourceFactory.php
└── ResourceKeyResolver.php
├── Responder.php
├── ResponderServiceProvider.php
├── Serializers
├── ErrorSerializer.php
├── NoopSerializer.php
└── SuccessSerializer.php
├── Testing
└── MakesApiRequests.php
├── TransformBuilder.php
├── Transformation.php
├── Transformers
├── ArrayTransformer.php
├── Concerns
│ ├── HasRelationships.php
│ ├── MakesResources.php
│ └── OverridesFractal.php
├── Transformer.php
└── TransformerResolver.php
└── helpers.php
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 3.3.0 (2023-03-14)
2 |
3 | ### Features
4 |
5 | * Add Laravel 10.0 support
6 | * Add Fractal 0.20 support
7 |
8 | # 3.2.0 (2022-03-25)
9 |
10 | ### Features
11 |
12 | * Add Laravel 9.0 support
13 |
14 | # 3.1.3 (2021-01-07)
15 |
16 | ### Features
17 |
18 | * Add PHP 8.0 support
19 |
20 | # 3.1.2 (2020-09-10)
21 |
22 | ### Features
23 |
24 | * Add Laravel 8.0 support
25 |
26 | # 3.1.1 (2020-03-18)
27 |
28 | ### Bug Fixes
29 |
30 | * Remove typehint from exception handler
31 |
32 | # 3.1.0 (2020-03-09)
33 |
34 | ### Features
35 |
36 | * Add Laravel 7.0 support
37 |
38 | # 3.0.6 (2019-08-28)
39 |
40 | ### Features
41 |
42 | * Add Laravel 6.0 support
43 |
44 | # 3.0.5 (2019-03-01)
45 |
46 | ### Features
47 |
48 | * Add Laravel 5.8 support
49 |
50 | # 3.0.4 (2018-09-05)
51 |
52 | ### Bug Fixes
53 |
54 | * Add missing header call in `ConvertsExceptions`
55 |
56 | # 3.0.3 (2018-09-05)
57 |
58 | ### Features
59 |
60 | * Add Laravel 5.7 support
61 |
62 | # 3.0.2 (2018-02-06)
63 |
64 | ### Bug Fixes
65 |
66 | * Change Collection's `intersectKey` with `array_key_intersect` in transformer as the method isn't available in all Laravel versions
67 |
68 | # 3.0.1 (2018-02-06)
69 |
70 | ### Features
71 |
72 | * When requesting non-whitelisted nested relations, it now returns any relation up to the one that's not whitelisted
73 | * It will now automatically camel case relations before loading them from the model allowing for snake cased relations in transformers
74 |
75 | ### Bug Fixes
76 |
77 | * Fix bug concerning circular relationship mappings
78 | * It will now correctly look for default relations in requested relations that are nested
79 |
80 | # 3.0.0 (2018-01-28)
81 |
82 | Version `3.0.0` contains many bug fixes, but also quite a lot of new features and changes. The entire relationship logic has been rewritten to improve performance, security and stability among other improvements. There has also been big focus on improving test coverage for this release and we're now right below 90% coverage.
83 |
84 | ### Breaking Changes
85 |
86 | * Fractal requirement changed to `0.17.0`
87 | * Whitelisted relationships now requires a transformer mapping in order for eager loading to take effect
88 | * Relationships will now only be eager loaded if you have specified a transformer with whitelisted relationships
89 | * The `transform` method of `Transformer` service and `Transformer` facade has been renamed to `make` and now returns a `TransformBuilder`
90 | * The `Flugg\Responder\Transformer` service has been renamed to `Transformation`
91 | * The `transform` helper function has been renamed to `transformation`
92 | * The `Transformer` facade has been renamed to `Transformation`
93 | * `NullSerializer` has been renamed to `NoopSerializer`
94 |
95 | ### Features
96 |
97 | * New integration test suite
98 | * Added support for primitive resources when including relations
99 | * Added a `fallback_transformer` configuration option to change the fallback transformer
100 | * Added a `error_message_files` configuration option to change translation files to load error messages from
101 | * Support for specifying query constraints for relationships as "load" methods in transformers
102 | * You no longer need to use the `resource` method inside "include" methods in transformers, you can just return the data directly
103 |
104 | ### Bug Fixes
105 |
106 | * It will now only eager load relationships that are whitelisted
107 | * You can now call multiple transformers in sequence without problems
108 | * Associative arrays will now be treated as an item rather than a collection
109 | * A default resource key of `data` is now set, allowing to use the `only` method even when data is empty
110 |
111 | # 2.0.14 (2018-01-23)
112 |
113 | ### Features
114 |
115 | * Added support for Laravel 5.6
116 |
117 | ### Bug Fixes
118 |
119 | * Removed extra end bracket in `PrettyPrintDecorator`
120 |
121 | # 2.0.13 (2018-01-23)
122 |
123 | ### Features
124 |
125 | * Added support for PHP 7.2
126 | * Added two new optional decorators: `PrettyPrintDecorator` and `EscapeHtmlDecorator`
127 |
128 | ### Security Fixes
129 |
130 | * New transformers now has an empty array as whitelisted relations instead of a wildcard
131 |
132 | ### Bug Fixes
133 |
134 | * Parameters are stripped away from relations before eager loading
135 | * Changed `TransformFactory` from singleton to a normal binding
136 | * `NullSerializer` now returns `null` instead of an empty array on null resources
137 | * Relations that have an "include" method in a transformer is no longer eager loaded
138 |
139 | # 2.0.12 (2017-10-17)
140 |
141 | ### Bug Fixes
142 |
143 | * Remove `string` typehint for `$errorCode`
144 |
145 | # 2.0.11 (2017-09-23)
146 |
147 | ### Bug Fixes
148 |
149 | * Change `Responder` and `Transformer` from singletons to regular bindings
150 |
151 | # 2.0.10 (2017-09-17)
152 |
153 | ### Bug Fixes
154 |
155 | * Rebind incompatible translator implementation with Lumen
156 |
157 | # 2.0.9 (2017-09-02)
158 |
159 | ### Bug Fixes
160 |
161 | * Add JSON check to exception handler
162 |
163 | # 2.0.8 (2017-08-17)
164 |
165 | ### Bug Fixes
166 |
167 | * Fix a query string relation parsing bug
168 |
169 | # 2.0.7 (2017-08-17)
170 |
171 | ### Bug Fixes
172 |
173 | * Fix explode default value for query string relations
174 |
175 | # 2.0.6 (2017-08-16)
176 |
177 | ### Bug Fixes
178 |
179 | * Explode query string relations on comma to support multiple relations
180 |
181 | # 2.0.5 (2017-08-16)
182 |
183 | ### Features
184 |
185 | * Automatic resolving of resource key if the data contains models
186 |
187 | ### Bug Fixes
188 |
189 | * Add missing `LogicException` import to base `Transformer`
190 | * Add missing `fields` key to error data of `ValidationFailedException`
191 |
192 | # 2.0.4 (2017-08-15)
193 |
194 | ### Bug Fixes
195 |
196 | * Change `Translator` contract with implementation to widen Laravel support
197 |
198 | # 2.0.3 (2017-08-11)
199 |
200 | ### Bug Fixes
201 |
202 | * Add missing `only` method to `SuccessResponseBuilder`
203 |
204 | # 2.0.2 (2017-08-11)
205 |
206 | ### Bug Fixes
207 |
208 | * Fix null data being converted to arrays for error responses
209 |
210 | # 2.0.1 (2017-08-11)
211 |
212 | ### Bug Fixes
213 |
214 | * Convert empty string messages in `HttpException` to null
215 | * Remove `data` field from error response to make it behave as stated in the documentation
216 |
217 | # 2.0.0 (2017-08-10)
218 |
219 | Version `2.0.0` has been a complete rewrite of the package and brings a lot new stuff to the table, including this very new changelog. The documentation has also been revamped and explains all the new features in greater details. If you're upgrading from an earlier version, make sure to remove your `config/responder.php` file and rerun `php artisan vendor:publish --provider="Flugg\Responder\ResponderServiceProvider"` to publish the new configuration file.
220 |
221 | ### Breaking Changes
222 |
223 | * Fractal requirement changed to `0.16.0`
224 | * Moved `Flugg\Responder\Transformer` to `Flugg\Responder\Transformers\Transformer`
225 | * Changed `Flugg\Responder\Traits\RespondsWithJson` to `Flugg\Responder\Http\Controllers\MakesResponses`
226 | * Changed `Flugg\Responder\Traits\HandlesApiErrors` to `Flugg\Responder\Exceptions\ConvertsExceptions`
227 | * Moved `Flugg\Responder\Traits\MakesApiRequests` to `Flugg\Responder\Testing\MakesApiRequests`
228 | * Removed `Flugg\Responder\Traits\ConvertsParameter`, use new `ConvertToSnakeCase` middleware instead
229 | * Removed `Flugg\Responder\Traits\ThrowsApiErrors`, manually override form requests to replicate
230 | * Changed `Flugg\Responder\Exceptions\Http\ApiException` to `Flugg\Responder\Exceptions\Http\HttpException`
231 | * Renamed `$statusCode` property of the `HttpException` exceptions to `$status`
232 | * Removed `Flugg\Responder\Exceptions\Http\ResourceNotFoundException`, handler now points to `PageNotFoundException`
233 | * Renamed `Flugg\Responder\Serializers\ApiSerializer` to `Flugg\Responder\Serializers\SuccessSerializer`
234 | * Renamed `successResponse` method of the `MakesResponses` trait to `success`
235 | * Renamed `errorResponse` method of the `MakesResponses` trait to `error`
236 | * Return `SuccessResponseBuilder` from `success` method instead of `JsonResponse`
237 | * Return `ErrorResponseBuilder` from `error` method instead of `JsonResponse`
238 | * Renamed `include` method to `with` on `SuccessResponseBuilder`
239 | * Renamed `addMeta` method to `meta` on `SuccessResponseBuilder`
240 | * Removed `transform` method on `SuccessResponseBuilder`, use `success` instead
241 | * Removed `getManager` and `getResource` methods from `SuccessResponseBuilder`
242 | * Changed `transformer` method of the `Transformable` interface to non-static
243 | * Added an `include` prefix to include methods in transformers
244 | * Renamed `transformException` of exception handler trait to `convertDefaultException`
245 | * Renamed `renderApiError` of exception handler trait to `renderResponse`
246 |
247 | ### Features
248 |
249 | * Added configurable response decorators
250 | * Added a `recursion_limit` configuration option
251 | * Allow transforming raw arrays and collections
252 | * Allow sending transformers to the `success` method
253 | * Allow sending resources as data to the `success` method
254 | * Added a `only` method to `SuccessResponseBuilder` to replicate Fractal's `parseFieldsets`
255 | * Added a `cursor` method to `SuccessResponseBuilder` for setting cursors
256 | * Added a `paginator` method to `SuccessResponseBuilder` for setting paginators
257 | * Added a `without` method to `SuccessResponseBuilder` to replicate Fractal's `parseExcludes`
258 | * Relationships are now automatically eager loaded
259 | * Changed `with` method to allow eager loading closures
260 | * Added a `filter_fields_parameter` configuration option for automatic data filtering
261 | * Added a `PageNotFoundException` exception
262 | * Added a `page_not_found` default error code
263 | * Added a `ConvertToSnakeCase` middleware to convert request parameters to snake case
264 | * Added a `Flugg\Responder\Transformer` service to transform without serializing
265 | * Added a `Transformer` facade to transform without serializing
266 | * Added a `transform` helper method to transform without serializing
267 | * Added a `NullSerializer` serializer to serialize without modifying the data
268 | * Added an `ErrorSerializer` contract for serializing errors
269 | * Added a default `Flugg\Responder\Serializers\ErrorSerializer`
270 | * Added a `$load` property to transformers to replicate Fractal's `$defaultIncludes`
271 | * Added a dynamic method in transformers to filter relations: `filterRelationName`
272 | * Allow converting custom exceptions using the `convert` method of the `ConvertsExceptions` trait
273 | * Added a shortcut `-m` to the `--model` modifier of the `make:transformer` command
274 | * Added a `--plain` (and `-p`) option to `make:transformer` to make plain transformers
275 | * Added possibility to bind transformers to models using the `TransformerResolver` class
276 | * Added possibility to bind error messages to error codes using the `ErrorMessageResolver` class
277 | * Decoupled Fractal from the package by introducing a `TransformFactory` adapter
278 | * Changed `success` to transform using an item resource if passed a has-one relation
279 | * Added a `resource` method to the base `Transformer` for creating related resources
280 |
281 | ### Bug Fixes
282 |
283 | * Remove extra field added from deeply nested relations (fixes #33)
284 | * Relations are not eager loaded when automatically including relations (fixes #48)
285 |
286 | ### Performance Improvements
287 |
288 | * Add a new caching layer to transformers, increasing performance with deeply nested relations
289 | * The relation inclusion code has been drastically improved
290 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Alexander Tømmerås
4 |
5 | > Permission is hereby granted, free of charge, to any person obtaining a copy
6 | > of this software and associated documentation files (the "Software"), to deal
7 | > in the Software without restriction, including without limitation the rights
8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | > copies of the Software, and to permit persons to whom the Software is
10 | > furnished to do so, subject to the following conditions:
11 | >
12 | > The above copyright notice and this permission notice shall be included in all
13 | > copies or substantial portions of the Software.
14 | >
15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | > SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Laravel Responder is a package for building API responses, integrating [Fractal](https://github.com/thephpleague/fractal) into Laravel and Lumen. It can transform your data using transformers, create and serialize success- and error responses, handle exceptions and assist you with testing your responses.
14 |
15 | # Table of Contents
16 |
17 | - [Introduction](#introduction)
18 | - [Requirements](#requirements)
19 | - [Installation](#installation)
20 | - [Usage](#usage)
21 | - [Creating Responses](#creating-responses)
22 | - [Creating Success Responses](#creating-success-responses)
23 | - [Creating Transformers](#creating-transformers)
24 | - [Transforming Data](#transforming-data)
25 | - [Creating Error Responses](#creating-error-responses)
26 | - [Handling Exceptions](#handling-exceptions)
27 | - [Contributing](#contributing)
28 | - [Donating](#contributing)
29 | - [License](#license)
30 |
31 | # Introduction
32 |
33 | Laravel lets you return models directly from a controller method to convert it to JSON. This is a quick way to build APIs but leaves your database columns exposed. [Fractal](https://fractal.thephpleague.com), a popular PHP package from [The PHP League](https://thephpleague.com/), solves this by introducing transformers. However, it can be a bit cumbersome to integrate into the framework as seen below:
34 |
35 | ```php
36 | public function index()
37 | {
38 | $resource = new Collection(User::all(), new UserTransformer());
39 |
40 | return response()->json((new Manager)->createData($resource)->toArray());
41 | }
42 | ```
43 |
44 | Not _that_ bad, but we all get a little spoiled by Laravel's magic. Wouldn't it be better if we could refactor it to:
45 |
46 | ```php
47 | public function index()
48 | {
49 | return responder()->success(User::all())->respond();
50 | }
51 | ```
52 |
53 | The package will allow you to do this and much more. The goal has been to create a high-quality package that feels like native Laravel. A package that lets you embrace the power of Fractal, while hiding it behind beautiful abstractions. There has also been put a lot of focus and thought to the documentation. Happy exploration!
54 |
55 | # Requirements
56 |
57 | This package requires:
58 | - PHP __7.0__+
59 | - Laravel __5.1__+ or Lumen __5.1__+
60 |
61 | # Installation
62 |
63 | To get started, install the package through Composer:
64 |
65 | ```shell
66 | composer require flugger/laravel-responder
67 | ```
68 |
69 | ## Laravel
70 |
71 | #### Register Service Provider
72 |
73 | Append the following line to the `providers` key in `config/app.php` to register the package:
74 |
75 | ```php
76 | Flugg\Responder\ResponderServiceProvider::class,
77 | ```
78 |
79 | ***
80 | _The package supports auto-discovery, so if you use Laravel 5.5 or later you may skip registering the service provider and facades as they will be registered automatically._
81 | ***
82 |
83 | #### Register Facades _(optional)_
84 |
85 | If you like facades, you may also append the `Responder` and `Transformation` facades to the `aliases` key:
86 |
87 | ```php
88 | 'Responder' => Flugg\Responder\Facades\Responder::class,
89 | 'Transformation' => Flugg\Responder\Facades\Transformation::class,
90 | ```
91 |
92 | #### Publish Package Assets _(optional)_
93 |
94 | You may additionally publish the package configuration and language file using the `vendor:publish` Artisan command:
95 |
96 | ```shell
97 | php artisan vendor:publish --provider="Flugg\Responder\ResponderServiceProvider"
98 | ```
99 |
100 | This will publish a `responder.php` configuration file in your `config` folder. It will also publish an `errors.php` file inside your `lang/en` folder which can be used for storing error messages.
101 |
102 | ## Lumen
103 |
104 | #### Register Service Provider
105 |
106 | Add the following line to `app/bootstrap.php` to register the package:
107 |
108 | ```php
109 | $app->register(Flugg\Responder\ResponderServiceProvider::class);
110 | ```
111 |
112 | #### Register Facades _(optional)_
113 |
114 | You may also add the following lines to `app/bootstrap.php` to register the facades:
115 |
116 | ```php
117 | class_alias(Flugg\Responder\Facades\Responder::class, 'Responder');
118 | class_alias(Flugg\Responder\Facades\Transformation::class, 'Transformation');
119 | ```
120 |
121 | #### Publish Package Assets _(optional)_
122 |
123 | Seeing there is no `vendor:publish` command in Lumen, you will have to create your own `config/responder.php` file if you want to configure the package.
124 |
125 | # Usage
126 |
127 | This documentation assumes some knowledge of how [Fractal](https://github.com/thephpleague/fractal) works.
128 |
129 | ## Creating Responses
130 |
131 | The package has a `Responder` service class, which has a `success` and `error` method to build success- and error responses respectively. To use the service and begin creating responses, pick one of the options below:
132 |
133 | #### Option 1: Inject `Responder` Service
134 |
135 | You may inject the `Flugg\Responder\Responder` service class directly into your controller methods:
136 |
137 | ```php
138 | public function index(Responder $responder)
139 | {
140 | return $responder->success();
141 | }
142 | ```
143 |
144 | You can also use the `error` method to create error responses:
145 |
146 | ```php
147 | return $responder->error();
148 | ```
149 |
150 | #### Option 2: Use `responder` Helper
151 |
152 | If you're a fan of Laravel's `response` helper function, you may like the `responder` helper function:
153 |
154 | ```php
155 | return responder()->success();
156 | ```
157 | ```php
158 | return responder()->error();
159 | ```
160 |
161 | #### Option 3: Use `Responder` Facade
162 |
163 | Optionally, you may use the `Responder` facade to create responses:
164 |
165 | ```php
166 | return Responder::success();
167 | ```
168 | ```php
169 | return Responder::error();
170 | ```
171 |
172 | #### Option 4: Use `MakesResponses` Trait
173 |
174 | Lastly, the package provides a `Flugg\Responder\Http\MakesResponses` trait you can use in your controllers:
175 |
176 | ```php
177 | return $this->success();
178 | ```
179 |
180 | ```php
181 | return $this->error();
182 | ```
183 |
184 | ***
185 | _Which option you pick is up to you, they are all equivalent, the important thing is to stay consistent. The helper function (option 2) will be used for the remaining of the documentation._
186 | ***
187 |
188 | ### Building Responses
189 |
190 | The `success` and `error` methods return a `SuccessResponseBuilder` and `ErrorResponseBuilder` respectively, which both extend an abstract `ResponseBuilder`, giving them common behaviors. They will be converted to JSON when returned from a controller, but you can explicitly create an instance of `Illuminate\Http\JsonResponse` with the `respond` method:
191 |
192 | ```php
193 | return responder()->success()->respond();
194 | ```
195 |
196 | ```php
197 | return responder()->error()->respond();
198 | ```
199 |
200 | The status code is set to `200` by default, but can be changed by setting the first parameter. You can also pass a list of headers as the second argument:
201 |
202 | ```php
203 | return responder()->success()->respond(201, ['x-foo' => true]);
204 | ```
205 |
206 | ```php
207 | return responder()->error()->respond(404, ['x-foo' => false]);
208 | ```
209 |
210 | ***
211 | _Consider always using the `respond` method for consistency's sake._
212 | ***
213 |
214 | ### Casting Response Data
215 |
216 | Instead of converting the response to a `JsonResponse` using the `respond` method, you can cast the response data to a few other types, like an array:
217 |
218 | ```php
219 | return responder()->success()->toArray();
220 | ```
221 |
222 | ```php
223 | return responder()->error()->toArray();
224 | ```
225 |
226 | You also have a `toCollection` and `toJson` method at your disposal.
227 |
228 | ### Decorating Response
229 |
230 | 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:
231 |
232 | ```php
233 | 'decorators' => [
234 | \Flugg\Responder\Http\Responses\Decorators\StatusCodeDecorator::class,
235 | \Flugg\Responder\Http\Responses\Decorators\SuccessFlagDecorator::class,
236 | ],
237 | ```
238 |
239 | 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:
240 |
241 | ```php
242 | return responder()->success()->decorator(ExampleDecorator::class)->respond();
243 | ```
244 | ```php
245 | return responder()->error()->decorator(ExampleDecorator::class)->respond();
246 | ```
247 |
248 | ***
249 |
250 | The package also ships with some situational decorators disabled by default, but which can be added to the decorator list:
251 | - `PrettyPrintDecorator` decorator will beautify the JSON output;
252 |
253 | ```php
254 | \Flugg\Responder\Http\Responses\Decorators\PrettyPrintDecorator::class,
255 | ```
256 |
257 | - `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.
258 |
259 | ```php
260 | \Flugg\Responder\Http\Responses\Decorators\EscapeHtmlDecorator::class,
261 | ```
262 |
263 | ***
264 |
265 | ## Creating Success Responses
266 |
267 | As briefly demonstrated above, success responses are created using the `success` method:
268 |
269 | ```php
270 | return responder()->success()->respond();
271 | ```
272 |
273 | Assuming no changes have been made to the configuration, the above code would output the following JSON:
274 |
275 | ```json
276 | {
277 | "status": 200,
278 | "success": true,
279 | "data": null
280 | }
281 | ```
282 |
283 | ### Setting Response Data
284 |
285 | The `success` method takes the response data as the first argument:
286 |
287 | ```php
288 | return responder()->success(Product::all())->respond();
289 | ```
290 |
291 | It accepts the same data types as you would normally return from your controllers, however, it also supports query builder and relationship instances:
292 |
293 | ```php
294 | return responder()->success(Product::where('id', 1))->respond();
295 | ```
296 |
297 | ```php
298 | return responder()->success(Product::first()->shipments())->respond();
299 | ```
300 |
301 | ***
302 | _The package will run the queries and convert them to collections behind the scenes._
303 | ***
304 |
305 | ### Transforming Response Data
306 |
307 | The response data will be transformed with Fractal if you've attached a transformer to the response. There are two ways to attach a transformer; either _explicitly_ by setting it on the response, or _implicitly_ by binding it to a model. Let's look at both ways in greater detail.
308 |
309 | #### Setting Transformer On Response
310 |
311 | You can attach a transformer to the response by sending a second argument to the `success` method. For instance, below we're attaching a simple closure transformer, transforming a list of products to only output their names:
312 |
313 | ```php
314 | return responder()->success(Product::all(), function ($product) {
315 | return ['name' => $product->name];
316 | })->respond();
317 | ```
318 |
319 | You may also transform using a dedicated transformer class:
320 |
321 | ```php
322 | return responder()->success(Product::all(), ProductTransformer::class)->respond();
323 | ```
324 |
325 | ```php
326 | return responder()->success(Product::all(), new ProductTransformer)->respond();
327 | ```
328 |
329 | ***
330 | _You can read more about creating dedicated transformer classes in the [Creating Transformers](#creating-transformers) chapter._
331 | ***
332 |
333 | #### Binding Transformer To Model
334 |
335 | If no transformer is set, the package will search the response data for an element implementing the `Flugg\Responder\Contracts\Transformable` interface to resolve a transformer from. You can take use of this by implementing the `Transformable` interface in your models:
336 |
337 | ```php
338 | class Product extends Model implements Transformable {}
339 | ```
340 |
341 | You can satisfy the contract by adding a `transformer` method that returns the corresponding transformer:
342 |
343 | ```php
344 | /**
345 | * Get a transformer for the class.
346 | *
347 | * @return \Flugg\Responder\Transformers\Transformer|string|callable
348 | */
349 | public function transformer()
350 | {
351 | return ProductTransformer::class;
352 | }
353 | ```
354 |
355 | ***
356 | _You're not limited to returning a class name string, you can return a transformer instance or closure transformer, just like the second parameter of the `success` method._
357 | ***
358 |
359 | Instead of implementing the `Transformable` contract for all models, an alternative approach is to bind the transformers using the `bind` method on the `TransformerResolver` class. You can place the code below within `AppServiceProvider` or an entirely new `TransformerServiceProvider`:
360 |
361 | ```php
362 | use Flugg\Responder\Contracts\Transformers\TransformerResolver;
363 |
364 | public function boot()
365 | {
366 | $this->app->make(TransformerResolver::class)->bind([
367 | \App\Product::class => \App\Transformers\ProductTransformer::class,
368 | \App\Shipment::class => \App\Transformers\ShipmentTransformer::class,
369 | ]);
370 | }
371 | ```
372 |
373 | After you've bound a transformer to a model you can skip the second parameter and still transform the data:
374 |
375 | ```php
376 | return responder()->success(Product::all())->respond();
377 | ```
378 |
379 | ***
380 | _As you might have noticed, unlike Fractal, you don't need to worry about creating resource objects like `Item` and `Collection`. The package will make one for you based on the data type, however, you may wrap your data in a resource object to override this._
381 | ***
382 |
383 | ### Setting Resource Key
384 |
385 | If the data you send into the response is a model or contains a list of models, a resource key will implicitly be resolved from the model's table name. You can overwrite this by adding a `getResourceKey` method to your model:
386 |
387 | ```php
388 | public function getResourceKey(): string {
389 | return 'products';
390 | }
391 | ```
392 |
393 | You can also explicitly set a resource key on a response by sending a third argument to the ´success` method:
394 |
395 | ```php
396 | return responder()->success(Product::all(), ProductTransformer::class, 'products')->respond();
397 | ```
398 |
399 | ### Paginating Response Data
400 |
401 | 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.
402 |
403 | ```php
404 | return responder()->success(Product::paginate())->respond();
405 | ```
406 |
407 | Assuming there are no products and the default configuration is used, the JSON output would look like:
408 |
409 | ```json
410 | {
411 | "success": true,
412 | "status": 200,
413 | "data": [],
414 | "pagination": {
415 | "total": 0,
416 | "count": 0,
417 | "perPage": 15,
418 | "currentPage": 1,
419 | "totalPages": 1,
420 | "links": []
421 | }
422 | }
423 | ```
424 |
425 | #### Setting Paginator On Response
426 |
427 | 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`:
428 |
429 | ```php
430 | $paginator = Product::paginate();
431 | $adapter = new IlluminatePaginatorAdapter($paginator);
432 |
433 | return responder()->success($paginator->getCollection())->paginator($adapter)->respond();
434 | ```
435 |
436 | #### Setting Cursor On Response
437 |
438 | You can also set cursors using the `cursor` method, expecting an instance of `League\Fractal\Pagination\Cursor`:
439 |
440 | ```php
441 | if ($request->has('cursor')) {
442 | $products = Product::where('id', '>', request()->cursor)->take(request()->limit)->get();
443 | } else {
444 | $products = Product::take(request()->limit)->get();
445 | }
446 |
447 | $cursor = new Cursor(request()->cursor, request()->previous, $products->last()->id ?? null, Product::count());
448 |
449 | return responder()->success($products)->cursor($cursor)->respond();
450 | ```
451 |
452 | ### Including Relationships
453 |
454 | If a transformer class is attached to the response, you can include relationships using the `with` method:
455 |
456 | ```php
457 | return responder()->success(Product::all())->with('shipments')->respond();
458 | ```
459 |
460 | You can send multiple arguments and specify nested relations using dot notation:
461 |
462 | ```php
463 | return responder()->success(Product::all())->with('shipments', 'orders.customer')->respond();
464 | ```
465 |
466 | 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:
467 |
468 | ```php
469 | return responder()->success(Product::all())->with(['shipments' => function ($query) {
470 | $query->whereNull('shipped_at');
471 | }])->respond();
472 | ```
473 |
474 | #### Including From Query String
475 |
476 | 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:
477 |
478 | ```
479 | GET /products?with=shipments,orders.customer
480 | ```
481 |
482 | #### Excluding Default Relations
483 |
484 | In your transformer classes, you may specify relations to automatically load. You may disable any of these relations using the `without` method:
485 |
486 | ```php
487 | return responder()->success(Product::all())->without('comments')->respond();
488 | ```
489 |
490 | ### Filtering Transformed Data
491 |
492 | 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:
493 |
494 | ```php
495 | return responder()->success(Product::all())->only('id', 'name')->respond();
496 | ```
497 |
498 | 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
499 |
500 | ```php
501 | return responder()->success(Product::all())->with('shipments')->only([
502 | 'products' => ['id', 'name'],
503 | 'shipments' => ['id']
504 | ])->respond();
505 | ```
506 |
507 | #### Filtering From Query String
508 |
509 | 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:
510 |
511 | ```
512 | GET /products?only=id,name
513 | ```
514 |
515 | You may automatically filter related resources by setting the parameter to a key-based array:
516 |
517 | ```
518 | GET /products?with=shipments&only[products]=id,name&only[shipments]=id
519 | ```
520 |
521 | ### Adding Meta Data
522 |
523 | You may want to attach additional meta data to your response. You can do this using the `meta` method:
524 |
525 | ```php
526 | return responder()->success(Product::all())->meta(['count' => Product::count()])->respond();
527 | ```
528 |
529 | When using the default serializer, the meta data will simply be appended to the response array:
530 |
531 | ```json
532 | {
533 | "success": true,
534 | "status": 200,
535 | "data": [],
536 | "count": 0
537 | }
538 | ```
539 |
540 | ### Serializing Response Data
541 |
542 | 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:
543 |
544 | ```php
545 | return responder()->success()->serializer(JsonApiSerializer::class)->respond();
546 | ```
547 |
548 | ```php
549 | return responder()->success()->serializer(new JsonApiSerializer())->respond();
550 | ```
551 |
552 | 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](http://fractal.thephpleague.com/serializers/).
553 |
554 | ## Creating Transformers
555 |
556 | 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:
557 |
558 | ```shell
559 | php artisan make:transformer ProductTransformer
560 | ```
561 |
562 | The command will generate a new `ProductTransformer.php` file in the `app/Transformers` folder:
563 |
564 | ```php
565 | (int) $product->id,
598 | ];
599 | }
600 | }
601 | ```
602 |
603 | 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:
604 |
605 | ```shell
606 | php artisan make:transformer ProductTransformer --model="App\Models\Product"
607 | ```
608 |
609 | #### Creating Plain Transformers
610 |
611 | 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:
612 |
613 | ```shell
614 | php artisan make:transformer ProductTransformer --plain
615 | ```
616 |
617 | This will remove the typehint from the `transform` method and add less boilerplate code.
618 |
619 | ### Setting Relationships
620 |
621 | 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:
622 |
623 | ```php
624 | protected $relations = [
625 | 'shipments' => ShipmentTransformer::class,
626 | ];
627 | ```
628 |
629 | ***
630 | _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._
631 | ***
632 |
633 | #### Setting Whitelisted Relationships
634 |
635 | The `$relations` property specifies a list of relations available to be included. You can set a list of relations mapped to their corresponding transformers:
636 |
637 | ```php
638 | protected $relations = [
639 | 'shipments' => ShipmentTransformer::class,
640 | 'orders' => OrderTransformer::class,
641 | ];
642 | ```
643 |
644 | #### Setting Autoloaded Relationships
645 |
646 | The `$load` property specifies a list of relations to be autoloaded every time you transform data with the transformer:
647 |
648 | ```php
649 | protected $load = [
650 | 'shipments' => ShipmentTransformer::class,
651 | 'orders' => OrderTransformer::class,
652 | ];
653 | ```
654 |
655 | ***
656 | _You don't have to add relations to both `$relations` and `$load`, all relations in `$load` will be available by nature._
657 | ***
658 |
659 | ### Including Relationships
660 |
661 | 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:
662 |
663 | ```php
664 | /**
665 | * Include related shipments.
666 | *
667 | * @param \App\Product $product
668 | * @return mixed
669 | */
670 | public function includeShipments(Product $product)
671 | {
672 | return $product->shipments;
673 | }
674 | ```
675 |
676 | Unlike Fractal you can just return the data directly without wrapping it in an `item` or `collection` method.
677 |
678 | ***
679 | _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._
680 | ***
681 |
682 | #### Using Include Parameters
683 |
684 | 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](https://fractal.thephpleague.com/transformers/#include-parameters). You may access the parameters by adding a second parameter to the "include" method:
685 |
686 | ```php
687 | public function includeShipments(Product $product, Collection $parameters)
688 | {
689 | return $product->shipments->take($parameters->get('limit'));
690 | }
691 | ```
692 |
693 | ***
694 | _To be as decoupled from Fractal as possible the parameters (which are normally accessed using `League\Fractal\ParamBag`) are accessed as Laravel collections instead._
695 | ***
696 |
697 | #### Adding Query Constraints
698 |
699 | 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.
700 |
701 | ```php
702 | /**
703 | * Load shipments with constraints.
704 | *
705 | * @param \Illuminate\Database\Eloquent\Builder $query
706 | * @return \Illuminate\Database\Eloquent\Builder
707 | */
708 | public function loadShipments($query)
709 | {
710 | return $query->whereNull('shipped_at');
711 | }
712 | ```
713 |
714 | ***
715 | _Note: You cannot mix "include" and "load" methods because the package doesn't eager load relationships included with an "include" method._
716 | ***
717 |
718 | ### Filtering Relationships
719 |
720 | 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:
721 |
722 | ```php
723 | /**
724 | * Filter included shipments.
725 | *
726 | * @param \Illuminate\Database\Eloquent\Collection $shipments
727 | * @return \Illuminate\Support\Collection
728 | */
729 | public function filterShipments($shipments)
730 | {
731 | return $shipments->filter(function ($shipment) {
732 | return is_null($shipment->shipped_at);
733 | });
734 | }
735 | ```
736 |
737 | ## Transforming Data
738 |
739 | 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.
740 |
741 | It's possible to simply transform data by newing up the transformer and calling `transform`:
742 |
743 | ```php
744 | return (new ProductTransformer)->transform(Product::first());
745 | ```
746 |
747 | However, this approach might become a bit messy when building transformations with relationships:
748 |
749 | ```php
750 | return array_merge((new ProductTransformer)->transform($product = Product::first()), [
751 | 'shipments' => $product->shipments->map(function ($shipment) {
752 | return (new ShipmentTransformer)->transform($shipment);
753 | })
754 | ]);
755 | ```
756 |
757 | Yuck! Imagine that with multiple nested relationships. Let's explore a simpler way to handle this.
758 |
759 | ### Building Transformations
760 |
761 | 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:
762 |
763 | ```php
764 | public function index(TransformBuilder $transformation)
765 | {
766 | return $transformation->resource(Product::all())->with('shipments')->transform();
767 | }
768 | ```
769 |
770 | 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`.
771 |
772 | ***
773 | _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._
774 | ***
775 |
776 | ### Transforming Without Serializing
777 |
778 | 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:
779 |
780 | ```php
781 | return $transformation->resource(Product::all())->serializer(NoopSerializer::class)->transform();
782 | ```
783 |
784 | 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.
785 |
786 | #### Option 1: The `Transformation` Service
787 |
788 | 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:
789 |
790 | ```php
791 | public function __construct(Transformation $transformation)
792 | {
793 | $transformation->make(Product::all())->with('shipments')->transform();
794 | }
795 | ```
796 |
797 | #### Option 2: The `transformation` Helper
798 |
799 | You can use the `transformation` helper function to transform data without serializing:
800 |
801 | ```php
802 | transformation(Product::all())->with('shipments')->transform();
803 | ```
804 |
805 | #### Option 3: The `Transformation` Facade
806 |
807 | You can also use the `Transformation` facade to achieve the same thing:
808 |
809 | ```php
810 | Transformation::make(Product::all())->with('shipments')->transform();
811 | ```
812 |
813 | ### Transforming To Camel Case
814 |
815 | 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:
816 |
817 | ```php
818 | return responder()->success(Product::all(), function ($product) {
819 | return ['soldOut' => (bool) $product->sold_out];
820 | })->respond();
821 | ```
822 |
823 | #### Transforming Request Parameters
824 |
825 | 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:
826 |
827 | ```php
828 | protected $middleware = [
829 | // ...
830 | \Flugg\Responder\Http\Middleware\ConvertToSnakeCase::class,
831 | ];
832 | ```
833 |
834 | ***
835 | _The middleware will run before request validation, so you should specify your validation rules in snake case as well._
836 | ***
837 |
838 | ## Creating Error Responses
839 |
840 | Whenever a consumer of your API does something unexpected, you can return an error response describing the problem. As briefly shown in a previous chapter, an error response can be created using the `error` method:
841 |
842 | ```php
843 | return responder()->error()->respond();
844 | ```
845 |
846 | The error response has knowledge about an error code, a corresponding error message and optionally some error data. With the default configuration, the above code would output the following JSON:
847 |
848 | ```json
849 | {
850 | "success": false,
851 | "status": 500,
852 | "error": {
853 | "code": null,
854 | "message": null
855 | }
856 | }
857 | ```
858 |
859 | ### Setting Error Code & Message
860 |
861 | You can fill the first parameter of the `error` method to set an error code:
862 |
863 | ```php
864 | return responder()->error('sold_out_error')->respond();
865 | ```
866 |
867 | ***
868 | _You may optionally use integers for error codes._
869 | ***
870 |
871 | In addition, you may set the second parameter to an error message describing the error:
872 |
873 | ```php
874 | return responder()->error('sold_out_error', 'The requested product is sold out.')->respond();
875 | ```
876 |
877 | #### Set Messages In Language Files
878 |
879 | 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:
880 |
881 | ```php
882 | return [
883 | 'sold_out_error' => 'The requested product is sold out.',
884 | ];
885 | ```
886 |
887 | #### Register Messages Using `ErrorMessageResolver`
888 |
889 | 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`:
890 |
891 | ```php
892 | use Flugg\Responder\ErrorMessageResolver;
893 |
894 | public function boot()
895 | {
896 | $this->app->make(ErrorMessageResolver::class)->register([
897 | 'sold_out_error' => 'The requested product is sold out.',
898 | ]);
899 | }
900 | ```
901 |
902 | ### Adding Error Data
903 |
904 | You may want to set additional data on the error response. Like in the example below, we're returning a list of shipments with the `sold_out` error response, giving the consumer information about when a new shipment for the product might arrive.
905 |
906 | ```php
907 | return responder()->error('sold_out')->data(['shipments' => Shipment::all()])->respond();
908 | ```
909 |
910 | The error data will be appended to the response data. Assuming we're using the default serializer and there are no shipments in the database, the code above would look like:
911 |
912 | ```json
913 | {
914 | "success": false,
915 | "status": 500,
916 | "error": {
917 | "code": "sold_out",
918 | "message": "The requested product is sold out.",
919 | "shipments": []
920 | }
921 | }
922 | ```
923 |
924 | ### Serializing Response Data
925 |
926 | Similarly to success responses, error responses will be serialized using the specified error serializer in the configuration file. This defaults to the package's own `Flugg\Responder\Serializers\ErrorSerializer`, but can of course be changed by using the `serializer` method:
927 |
928 | ```php
929 | return responder()->error()->serializer(ExampleErrorSerializer::class)->respond();
930 | ```
931 |
932 | ```php
933 | return responder()->success()->serializer(new ExampleErrorSerializer())->respond();
934 | ```
935 |
936 | You can create your own error serializer by implementing the `Flugg\Responder\Contracts\ErrorSerializer` contract.
937 |
938 | ## Handling Exceptions
939 |
940 | No matter how much we try to avoid them, exceptions do happen. Responding to the exceptions in an elegant manner will improve the user experience of your API. The package can enhance your exception handler to automatically turn exceptions in to error responses. If you want to take use of this, you can either use the package's exception handler or include a trait as described in further details below.
941 |
942 | #### Option 1: Replace `Handler` Class
943 |
944 | To use the package's exception handler you need to replace the default import in `app/Exceptions/Handler.php`:
945 |
946 | ```php
947 | use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
948 | ```
949 |
950 | With the package's handler class:
951 |
952 | ```php
953 | use Flugg\Responder\Exceptions\Handler as ExceptionHandler;
954 | ```
955 |
956 | ***
957 | This will not work with Lumen as its exception handler is incompatible with Laravel's. Look instead at the second option below.
958 | ***
959 |
960 | #### Option 2: Use `ConvertsExceptions` Trait
961 |
962 | The package's exception handler uses the `Flugg\Responder\Exceptions\ConvertsExceptions` trait to load of most of its work. Instead of replacing the exception handler, you can use the trait in your own handler class. To replicate the behavior of the exception handler, you would also have to add the following code to the `render` method:
963 |
964 | ```php
965 | public function render($request, Exception $exception)
966 | {
967 | $this->convertDefaultException($exception);
968 |
969 | if ($exception instanceof HttpException) {
970 | return $this->renderResponse($exception);
971 | }
972 |
973 | return parent::render($request, $exception);
974 | }
975 | ```
976 |
977 | If you only want to return JSON error responses on requests actually asking for JSON, you may wrap the code above in a `wantsJson` check as seen below:
978 |
979 | ```php
980 | if ($request->wantsJson()) {
981 | $this->convertDefaultException($exception);
982 |
983 | if ($exception instanceof HttpException) {
984 | return $this->renderResponse($exception);
985 | }
986 | }
987 | ```
988 |
989 | ### Converting Exceptions
990 |
991 | 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.
992 |
993 | | Caught Exceptions | Converted To |
994 | | --------------------------------------------------------------- | ---------------------------- |
995 | | `Illuminate\Auth\AuthenticationException` | `UnauthenticatedException` |
996 | | `Illuminate\Auth\Access\AuthorizationException` | `UnauthorizedException` |
997 | | `Symfony\Component\HttpKernel\Exception\NotFoundHttpException` | `PageNotFoundException` |
998 | | `Illuminate\Database\Eloquent\ModelNotFoundException` | `PageNotFoundException` |
999 | | `Illuminate\Database\Eloquent\RelationNotFoundException` | `RelationNotFoundException` |
1000 | | `Illuminate\Validation\ValidationException` | `ValidationFailedException` |
1001 |
1002 | You can disable the conversions of some of the exceptions above using the `$dontConvert` property:
1003 |
1004 | ```php
1005 | /**
1006 | * A list of default exception types that should not be converted.
1007 | *
1008 | * @var array
1009 | */
1010 | protected $dontConvert = [
1011 | ModelNotFoundException::class,
1012 | ];
1013 | ```
1014 |
1015 | ***
1016 | If you're using the trait option, you can disable all the default conversions by removing the call to `convertDefaultException` in the `render` method.
1017 | ***
1018 |
1019 | #### Convert Custom Exceptions
1020 |
1021 | In addition to letting the package convert Laravel exceptions, you can also convert your own exceptions using the `convert` method in the `render` method:
1022 |
1023 | ```php
1024 | $this->convert($exception, [
1025 | InvalidValueException => PageNotFoundException,
1026 | ]);
1027 | ```
1028 |
1029 | You can optionally give it a closure that throws the new exception, if you want to give it constructor parameters:
1030 |
1031 | ```php
1032 | $this->convert($exception, [
1033 | MaintenanceModeException => function ($exception) {
1034 | throw new ServerDownException($exception->retryAfter);
1035 | },
1036 | ]);
1037 | ```
1038 |
1039 | ### Creating HTTP Exceptions
1040 |
1041 | An exception class is a convenient place to store information about an error. The package provides an abstract exception class `Flugg\Responder\Exceptions\Http\HttpException`, which has knowledge about status code, an error code and an error message. Continuing on our product example from above, we could create our own `HttpException` class:
1042 |
1043 | ```php
1044 | Shipment::all()
1087 | ];
1088 | }
1089 | ```
1090 |
1091 | If you're letting the package handle exceptions, you can now throw the exception anywhere in your application and it will automatically be rendered to an error response.
1092 |
1093 | ```php
1094 | throw new SoldOutException();
1095 | ```
1096 |
1097 | # Contributing
1098 |
1099 | Contributions are more than welcome and you're free to create a pull request on Github. You can run tests with the following command:
1100 |
1101 | ```shell
1102 | vendor/bin/phpunit
1103 | ```
1104 |
1105 | If you find bugs or have suggestions for improvements, feel free to submit an issue on Github. However, if it's a security related issue, please send an email to flugged@gmail.com instead.
1106 |
1107 | # Donating
1108 |
1109 | The package is completely free to use, however, a lot of time has been put into making it. If you want to show your appreciation by leaving a small donation, you can do so by clicking [here](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=PRMC9WLJY8E46&lc=NO&item_name=Laravel%20Responder¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted). Thanks!
1110 |
1111 | # License
1112 |
1113 | Laravel Responder is free software distributed under the terms of the MIT license. See [license.md](license.md) for more details.
1114 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flugger/laravel-responder",
3 | "description": "A Laravel Fractal package for building API responses, giving you the power of Fractal and the elegancy of Laravel.",
4 | "keywords": [
5 | "laravel",
6 | "lumen",
7 | "fractal",
8 | "transformer",
9 | "api",
10 | "response",
11 | "responder"
12 | ],
13 | "homepage": "https://github.com/flugger/laravel-responder",
14 | "license": "MIT",
15 | "authors": [
16 | {
17 | "name": "Alexander Tømmerås",
18 | "email": "flugged@gmail.com"
19 | }
20 | ],
21 | "require": {
22 | "php": "^7.4|^8.0",
23 | "illuminate/contracts": "^5.1|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
24 | "illuminate/support": "^5.1|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
25 | "league/fractal": "^0.19.0|^0.20"
26 | },
27 | "require-dev": {
28 | "illuminate/database": "^5.1|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
29 | "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0|^10.0",
30 | "mockery/mockery": "^0.9.5|^1.0",
31 | "doctrine/dbal": "^2.5|^3.5|^4.2",
32 | "phpunit/phpunit": "^8.5|^9.0|^10.5|^11.5.3"
33 | },
34 | "autoload": {
35 | "psr-4": {
36 | "Flugg\\Responder\\": "src"
37 | },
38 | "files": [
39 | "src/helpers.php"
40 | ]
41 | },
42 | "autoload-dev": {
43 | "psr-4": {
44 | "Flugg\\Responder\\Tests\\": "tests"
45 | }
46 | },
47 | "config": {
48 | "sort-packages": true
49 | },
50 | "extra": {
51 | "laravel": {
52 | "providers": [
53 | "Flugg\\Responder\\ResponderServiceProvider"
54 | ],
55 | "aliases": {
56 | "Responder": "Flugg\\Responder\\Facades\\Responder",
57 | "Transformer": "Flugg\\Responder\\Facades\\Transformer"
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/config/responder.php:
--------------------------------------------------------------------------------
1 | [
17 | 'success' => Flugg\Responder\Serializers\SuccessSerializer::class,
18 | 'error' => \Flugg\Responder\Serializers\ErrorSerializer::class,
19 | ],
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Response Decorators
24 | |--------------------------------------------------------------------------
25 | |
26 | | Response decorators are used to decorate both your success- and error
27 | | responses. A decorator can be disabled by removing it from the list
28 | | below. You may additionally add your own decorators to the list.
29 | |
30 | */
31 |
32 | 'decorators' => [
33 | \Flugg\Responder\Http\Responses\Decorators\StatusCodeDecorator::class,
34 | \Flugg\Responder\Http\Responses\Decorators\SuccessFlagDecorator::class,
35 | ],
36 |
37 | /*
38 | |--------------------------------------------------------------------------
39 | | Fallback Transformer
40 | |--------------------------------------------------------------------------
41 | |
42 | | When transforming data without specifying a transformer we'll instead
43 | | use a fallback transformer specified below. The [ArrayTransformer]
44 | | transformer will simply convert the data to an array untouched.
45 | |
46 | */
47 |
48 | 'fallback_transformer' => \Flugg\Responder\Transformers\ArrayTransformer::class,
49 |
50 | /*
51 | |--------------------------------------------------------------------------
52 | | Load Relationships With Query String Parameter
53 | |--------------------------------------------------------------------------
54 | |
55 | | The package can automatically load relationships from the query string
56 | | and will look for a query string parameter with the name configured
57 | | below. You can set the value to null to disable the autoloading.
58 | |
59 | */
60 |
61 | 'load_relations_parameter' => 'with',
62 |
63 | /*
64 | |--------------------------------------------------------------------------
65 | | Filter Fields With Query String Parameter
66 | |--------------------------------------------------------------------------
67 | |
68 | | The package can automatically filter the fields of transformed data
69 | | from a query string parameter configured below. The technique is
70 | | also known as sparse fieldsets. Set it to null to disable it.
71 | |
72 | */
73 |
74 | 'filter_fields_parameter' => 'only',
75 |
76 | /*
77 | |--------------------------------------------------------------------------
78 | | Recursion Limit
79 | |--------------------------------------------------------------------------
80 | |
81 | | When transforming data, you may be including relations recursively.
82 | | By setting the value below, you can limit the amount of times it
83 | | should include relationships recursively. Five might be good.
84 | |
85 | */
86 |
87 | 'recursion_limit' => 5,
88 |
89 | /*
90 | |--------------------------------------------------------------------------
91 | | Error Message Translation Files
92 | |--------------------------------------------------------------------------
93 | |
94 | | You can declare error messages in a language file, which allows for
95 | | returning messages in different languages. The array below lists
96 | | the language files that will be searched in to find messages.
97 | |
98 | */
99 |
100 | 'error_message_files' => ['errors'],
101 |
102 | /*
103 | |--------------------------------------------------------------------------
104 | | CamelCase Relations
105 | |--------------------------------------------------------------------------
106 | |
107 | | By default laravel responder will convert relations request to camel-case
108 | | but some people would like to use snake-case, so you can set it below
109 | |
110 | */
111 |
112 | 'use_camel_case_relations' => true,
113 |
114 | ];
--------------------------------------------------------------------------------
/resources/lang/en/errors.php:
--------------------------------------------------------------------------------
1 | 'You are not authenticated for this request.',
17 | 'unauthorized' => 'You are not authorized for this request.',
18 | 'page_not_found' => 'The requested page does not exist.',
19 | 'relation_not_found' => 'The requested relation does not exist.',
20 | 'validation_failed' => 'The given data failed to pass validation.',
21 |
22 | ];
--------------------------------------------------------------------------------
/resources/stubs/transformer.model.stub:
--------------------------------------------------------------------------------
1 | (int) $DummyModelVariable->id,
34 | ];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/resources/stubs/transformer.plain.stub:
--------------------------------------------------------------------------------
1 |
15 | * @license The MIT License
16 | */
17 | class MakeTransformer extends GeneratorCommand
18 | {
19 | /**
20 | * The console command name.
21 | *
22 | * @var string
23 | */
24 | protected $name = 'make:transformer';
25 |
26 | /**
27 | * The console command description.
28 | *
29 | * @var string
30 | */
31 | protected $description = 'Create a new transformer class';
32 |
33 | /**
34 | * The type of class being generated.
35 | *
36 | * @var string
37 | */
38 | protected $type = 'Transformer';
39 |
40 | /**
41 | * Get the stub file for the generator.
42 | *
43 | * @return string
44 | */
45 | protected function getStub()
46 | {
47 | if ($this->option('plain')) {
48 | return __DIR__ . '/../../resources/stubs/transformer.plain.stub';
49 | }
50 |
51 | return __DIR__ . '/../../resources/stubs/transformer.model.stub';
52 | }
53 |
54 | /**
55 | * Get the default namespace for the class.
56 | *
57 | * @param string $rootNamespace
58 | * @return string
59 | */
60 | protected function getDefaultNamespace($rootNamespace)
61 | {
62 | return $rootNamespace . '\Transformers';
63 | }
64 |
65 | /**
66 | * Build the class with the given name.
67 | *
68 | * @param string $name
69 | * @return string
70 | */
71 | protected function buildClass($name)
72 | {
73 | $replace = [];
74 |
75 | if (! $this->option('model') && ! $this->option('plain')) {
76 | $this->input->setOption('model', $this->resolveModelFromClassName());
77 | }
78 |
79 | if ($this->option('model')) {
80 | $replace = $this->buildModelReplacements($replace);
81 | }
82 |
83 | return str_replace(array_keys($replace), array_values($replace), parent::buildClass($name));
84 | }
85 |
86 | /**
87 | * Resolve a model from the given class name.
88 | *
89 | * @return string
90 | */
91 | protected function resolveModelFromClassName()
92 | {
93 | return 'App\\Models\\' . str_replace('Transformer', '', Arr::last(explode('/', $this->getNameInput())));
94 | }
95 |
96 | /**
97 | * Build the model replacement values.
98 | *
99 | * @param array $replace
100 | * @return array
101 | */
102 | protected function buildModelReplacements(array $replace)
103 | {
104 | if (! class_exists($modelClass = $this->parseModel($this->option('model')))) {
105 | if ($this->confirm("A {$modelClass} model does not exist. Do you want to generate it?", true)) {
106 | $this->call('make:model', ['name' => $modelClass]);
107 | }
108 | }
109 |
110 | return array_merge($replace, [
111 | 'DummyFullModelClass' => $modelClass,
112 | 'DummyModelClass' => class_basename($modelClass),
113 | 'DummyModelVariable' => lcfirst(class_basename($modelClass)),
114 | ]);
115 | }
116 |
117 | /**
118 | * Get the fully-qualified model class name.
119 | *
120 | * @param string $model
121 | * @return string
122 | */
123 | protected function parseModel($model)
124 | {
125 | if (preg_match('([^A-Za-z0-9_/\\\\])', $model)) {
126 | throw new InvalidArgumentException('Model name contains invalid characters.');
127 | }
128 |
129 | $model = trim(str_replace('/', '\\', $model), '\\');
130 |
131 | if (! Str::startsWith($model, $rootNamespace = $this->laravel->getNamespace())) {
132 | $model = $rootNamespace . $model;
133 | }
134 |
135 | return $model;
136 | }
137 |
138 | /**
139 | * Get the console command options.
140 | *
141 | * @return array
142 | */
143 | protected function getOptions()
144 | {
145 | return [
146 | ['model', 'm', InputOption::VALUE_OPTIONAL, 'Generate a model transformer.'],
147 | ['plain', 'p', InputOption::VALUE_NONE, 'Generate a plain transformer.'],
148 | ];
149 | }
150 | }
--------------------------------------------------------------------------------
/src/Contracts/ErrorFactory.php:
--------------------------------------------------------------------------------
1 |
9 | * @license The MIT License
10 | */
11 | interface ErrorFactory
12 | {
13 | /**
14 | * Make an error array from the given error code, message and error data.
15 | *
16 | * @param \Flugg\Responder\Contracts\ErrorSerializer $serializer
17 | * @param mixed|null $errorCode
18 | * @param string|null $message
19 | * @param array|null $data
20 | * @return array
21 | */
22 | public function make(ErrorSerializer $serializer, $errorCode = null, ?string $message = null, ?array $data = null): array;
23 | }
24 |
--------------------------------------------------------------------------------
/src/Contracts/ErrorMessageResolver.php:
--------------------------------------------------------------------------------
1 |
10 | * @license The MIT License
11 | */
12 | interface ErrorMessageResolver
13 | {
14 | /**
15 | * Resolve a message from the given error code.
16 | *
17 | * @param mixed $errorCode
18 | * @return string|null
19 | */
20 | public function resolve($errorCode);
21 | }
--------------------------------------------------------------------------------
/src/Contracts/ErrorSerializer.php:
--------------------------------------------------------------------------------
1 |
9 | * @license The MIT License
10 | */
11 | interface ErrorSerializer
12 | {
13 | /**
14 | * Format the error data.
15 | *
16 | * @param mixed|null $errorCode
17 | * @param string|null $message
18 | * @param array|null $data
19 | * @return array
20 | */
21 | public function format($errorCode = null, ?string $message = null, ?array $data = null): array;
22 | }
23 |
--------------------------------------------------------------------------------
/src/Contracts/Pagination/PaginatorFactory.php:
--------------------------------------------------------------------------------
1 |
15 | * @license The MIT License
16 | */
17 | interface PaginatorFactory
18 | {
19 | /**
20 | * Make a Fractal paginator adapter from a Laravel paginator.
21 | *
22 | * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator
23 | * @return \League\Fractal\Pagination\PaginatorInterface
24 | */
25 | public function make(LengthAwarePaginator $paginator): PaginatorInterface;
26 |
27 | /**
28 | * Make a Fractal paginator adapter from a Laravel paginator.
29 | *
30 | * @param \Flugg\Responder\Pagination\CursorPaginator $paginator
31 | * @return \League\Fractal\Pagination\Cursor
32 | */
33 | public function makeCursor(CursorPaginator $paginator): Cursor;
34 | }
--------------------------------------------------------------------------------
/src/Contracts/Resources/ResourceFactory.php:
--------------------------------------------------------------------------------
1 |
11 | * @license The MIT License
12 | */
13 | interface ResourceFactory
14 | {
15 | /**
16 | * Make resource from the given data.
17 | *
18 | * @param mixed $data
19 | * @param \Flugg\Responder\Transformers\Transformer|string|callable|null $transformer
20 | * @param string|null $resourceKey
21 | * @return \League\Fractal\Resource\ResourceInterface
22 | */
23 | public function make($data = null, $transformer = null, ?string $resourceKey = null): ResourceInterface;
24 | }
25 |
--------------------------------------------------------------------------------
/src/Contracts/Resources/ResourceKeyResolver.php:
--------------------------------------------------------------------------------
1 |
10 | * @license The MIT License
11 | */
12 | interface ResourceKeyResolver
13 | {
14 | /**
15 | * Register a transformable to resource key binding.
16 | *
17 | * @param string|array $transformable
18 | * @param string $resourceKey
19 | * @return void
20 | */
21 | public function bind($transformable, string $resourceKey);
22 |
23 | /**
24 | * Resolve a resource key from the given data.
25 | *
26 | * @param mixed $data
27 | * @return string
28 | */
29 | public function resolve($data);
30 | }
--------------------------------------------------------------------------------
/src/Contracts/Responder.php:
--------------------------------------------------------------------------------
1 |
12 | * @license The MIT License
13 | */
14 | interface Responder
15 | {
16 | /**
17 | * Build a successful response.
18 | *
19 | * @param mixed $data
20 | * @param callable|string|\Flugg\Responder\Transformers\Transformer|null $transformer
21 | * @param string|null $resourceKey
22 | * @return \Flugg\Responder\Http\Responses\SuccessResponseBuilder
23 | */
24 | public function success($data = null, $transformer = null, ?string $resourceKey = null): SuccessResponseBuilder;
25 |
26 | /**
27 | * Build an error response.
28 | *
29 | * @param mixed|null $errorCode
30 | * @param string|null $message
31 | * @return \Flugg\Responder\Http\Responses\ErrorResponseBuilder
32 | */
33 | public function error($errorCode = null, ?string $message = null): ErrorResponseBuilder;
34 | }
35 |
--------------------------------------------------------------------------------
/src/Contracts/ResponseFactory.php:
--------------------------------------------------------------------------------
1 |
12 | * @license The MIT License
13 | */
14 | interface ResponseFactory
15 | {
16 | /**
17 | * Generate a JSON response.
18 | *
19 | * @param array $data
20 | * @param int $status
21 | * @param array $headers
22 | * @return \Illuminate\Http\JsonResponse
23 | */
24 | public function make(array $data, int $status, array $headers = []): JsonResponse;
25 | }
--------------------------------------------------------------------------------
/src/Contracts/SimpleTransformer.php:
--------------------------------------------------------------------------------
1 |
11 | * @license The MIT License
12 | */
13 | interface SimpleTransformer
14 | {
15 | /**
16 | * Transform the data without serializing, using the given transformer.
17 | *
18 | * @param mixed $data
19 | * @param \Flugg\Responder\Transformers\Transformer|callable|string|null $transformer
20 | * @param string|null $resourceKey
21 | * @return \Flugg\Responder\TransformBuilder
22 | */
23 | public function make($data = null, $transformer = null, ?string $resourceKey = null): TransformBuilder;
24 | }
25 |
--------------------------------------------------------------------------------
/src/Contracts/TransformFactory.php:
--------------------------------------------------------------------------------
1 |
13 | * @license The MIT License
14 | */
15 | interface TransformFactory
16 | {
17 | /**
18 | * Transform the given resource, and serialize the data with the given serializer.
19 | *
20 | * @param \League\Fractal\Resource\ResourceInterface $resource
21 | * @param \League\Fractal\Serializer\SerializerAbstract $serializer
22 | * @param array $options
23 | * @return array|null
24 | */
25 | public function make(ResourceInterface $resource, SerializerAbstract $serializer, array $options = []);
26 | }
--------------------------------------------------------------------------------
/src/Contracts/Transformable.php:
--------------------------------------------------------------------------------
1 |
10 | * @license The MIT License
11 | */
12 | interface Transformable
13 | {
14 | /**
15 | * Get a transformer for the class.
16 | *
17 | * @return \Flugg\Responder\Transformers\Transformer|callable|string|null
18 | */
19 | public function transformer();
20 | }
--------------------------------------------------------------------------------
/src/Contracts/Transformers/TransformerResolver.php:
--------------------------------------------------------------------------------
1 |
10 | * @license The MIT License
11 | */
12 | interface TransformerResolver
13 | {
14 | /**
15 | * Register a transformable to transformer binding.
16 | *
17 | * @param string|array $transformable
18 | * @param string|callback $transformer
19 | * @return void
20 | */
21 | public function bind($transformable, $transformer);
22 |
23 | /**
24 | * Resolve a transformer.
25 | *
26 | * @param \Flugg\Responder\Transformers\Transformer|string|callable $transformer
27 | * @return \Flugg\Responder\Transformers\Transformer|callable
28 | * @throws \Flugg\Responder\Exceptions\InvalidTransformerException
29 | */
30 | public function resolve($transformer);
31 |
32 | /**
33 | * Resolve a transformer from the given data.
34 | *
35 | * @param mixed $data
36 | * @return \Flugg\Responder\Transformers\Transformer|callable
37 | */
38 | public function resolveFromData($data);
39 | }
--------------------------------------------------------------------------------
/src/ErrorFactory.php:
--------------------------------------------------------------------------------
1 |
13 | * @license The MIT License
14 | */
15 | class ErrorFactory implements ErrorFactoryContract
16 | {
17 | /**
18 | * A resolver for resolving messages from error codes.
19 | *
20 | * @var \Flugg\Responder\Contracts\ErrorMessageResolver
21 | */
22 | protected $messageResolver;
23 |
24 | /**
25 | * Construct the factory class.
26 | *
27 | * @param \Flugg\Responder\Contracts\ErrorMessageResolver $messageResolver
28 | */
29 | public function __construct(ErrorMessageResolverContract $messageResolver)
30 | {
31 | $this->messageResolver = $messageResolver;
32 | }
33 |
34 | /**
35 | * Make an error array from the given error code and message.
36 | *
37 | * @param \Flugg\Responder\Contracts\ErrorSerializer $serializer
38 | * @param mixed|null $errorCode
39 | * @param string|null $message
40 | * @param array|null $data
41 | * @return array
42 | */
43 | public function make(ErrorSerializer $serializer, $errorCode = null, ?string $message = null, ?array $data = null): array
44 | {
45 | if (isset($errorCode) && ! isset($message)) {
46 | $message = $this->messageResolver->resolve($errorCode);
47 | }
48 |
49 | return $serializer->format($errorCode, $message, $data);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/ErrorMessageResolver.php:
--------------------------------------------------------------------------------
1 |
13 | * @license The MIT License
14 | */
15 | class ErrorMessageResolver implements ErrorMessageResolverContract
16 | {
17 | /**
18 | * A serivce for resolving messages from language files.
19 | *
20 | * @var \Illuminate\Translation\Translator
21 | */
22 | protected $translator;
23 |
24 | /**
25 | * A list of registered messages mapped to error codes.
26 | *
27 | * @var array
28 | */
29 | protected $messages = [];
30 |
31 | /**
32 | * Construct the resolver class.
33 | *
34 | * @param \Illuminate\Translation\Translator $translator
35 | */
36 | public function __construct(Translator $translator)
37 | {
38 | $this->translator = $translator;
39 | }
40 |
41 | /**
42 | * Register a message mapped to an error code.
43 | *
44 | * @param mixed $errorCode
45 | * @param string $message
46 | * @return void
47 | */
48 | public function register($errorCode, string $message)
49 | {
50 | $this->messages = array_merge($this->messages, is_array($errorCode) ? $errorCode : [
51 | $errorCode => $message,
52 | ]);
53 | }
54 |
55 | /**
56 | * Resolve a message from the given error code.
57 | *
58 | * @param mixed $errorCode
59 | * @return string|null
60 | */
61 | public function resolve($errorCode)
62 | {
63 | if (key_exists($errorCode, $this->messages)) {
64 | return $this->messages[$errorCode];
65 | }
66 |
67 | if ($this->translator->has($errorCode = "errors.$errorCode")) {
68 | return $this->translator->get($errorCode);
69 | }
70 |
71 | return null;
72 | }
73 | }
--------------------------------------------------------------------------------
/src/Exceptions/ConvertsExceptions.php:
--------------------------------------------------------------------------------
1 |
26 | * @license The MIT License
27 | */
28 | trait ConvertsExceptions
29 | {
30 | /**
31 | * A list of default exception types that should not be converted.
32 | *
33 | * @var array
34 | */
35 | protected $dontConvert = [];
36 |
37 | /**
38 | * Convert an exception to another exception
39 | *
40 | * @param \Exception|\Throwable $exception
41 | * @param array $convert
42 | * @return void
43 | */
44 | protected function convert($exception, array $convert)
45 | {
46 | foreach ($convert as $source => $target) {
47 | if ($exception instanceof $source) {
48 | if (is_callable($target)) {
49 | $target($exception);
50 | }
51 |
52 | throw new $target;
53 | }
54 | }
55 | }
56 |
57 | /**
58 | * Convert a default exception to an API exception.
59 | *
60 | * @param \Exception|\Throwable $exception
61 | * @return void
62 | */
63 | protected function convertDefaultException($exception)
64 | {
65 | $this->convert($exception, array_diff_key([
66 | AuthenticationException::class => UnauthenticatedException::class,
67 | AuthorizationException::class => UnauthorizedException::class,
68 | NotFoundHttpException::class => PageNotFoundException::class,
69 | ModelNotFoundException::class => PageNotFoundException::class,
70 | BaseRelationNotFoundException::class => RelationNotFoundException::class,
71 | ValidationException::class => function ($exception) {
72 | throw new ValidationFailedException($exception->validator);
73 | },
74 | ], array_flip($this->dontConvert)));
75 | }
76 |
77 | /**
78 | * Render an error response from an API exception.
79 | *
80 | * @param \Flugg\Responder\Exceptions\Http\HttpException $exception
81 | * @return \Illuminate\Http\JsonResponse
82 | */
83 | protected function renderResponse(HttpException $exception): JsonResponse
84 | {
85 | return app(Responder::class)
86 | ->error($exception->errorCode(), $exception->message())
87 | ->data($exception->data())
88 | ->respond($exception->statusCode(), $exception->getHeaders());
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/Exceptions/Handler.php:
--------------------------------------------------------------------------------
1 |
14 | * @license The MIT License
15 | */
16 | class Handler extends ExceptionHandler
17 | {
18 | use ConvertsExceptions;
19 |
20 | /**
21 | * Render an exception into an HTTP response.
22 | *
23 | * @param \Illuminate\Http\Request $request
24 | * @param \Exception|\Throwable $exception
25 | * @return \Symfony\Component\HttpFoundation\Response
26 | */
27 | public function render($request, $exception)
28 | {
29 | if ($request->wantsJson()) {
30 | $this->convertDefaultException($exception);
31 |
32 | if ($exception instanceof HttpException) {
33 | return $this->renderResponse($exception);
34 | }
35 | }
36 |
37 | return parent::render($request, $exception);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Exceptions/Http/HttpException.php:
--------------------------------------------------------------------------------
1 |
11 | * @license The MIT License
12 | */
13 | abstract class HttpException extends BaseHttpException
14 | {
15 | /**
16 | * An HTTP status code.
17 | *
18 | * @var int
19 | */
20 | protected $status = 500;
21 |
22 | /**
23 | * An error code.
24 | *
25 | * @var string|null
26 | */
27 | protected $errorCode = null;
28 |
29 | /**
30 | * An error message.
31 | *
32 | * @var string
33 | */
34 | protected $message = '';
35 |
36 | /**
37 | * Additional error data.
38 | *
39 | * @var array|null
40 | */
41 | protected $data = null;
42 |
43 | /**
44 | * Attached headers.
45 | *
46 | * @var array
47 | */
48 | protected $headers = [];
49 |
50 | /**
51 | * Construct the exception class.
52 | *
53 | * @param string|null $message
54 | * @param array|null $headers
55 | */
56 | public function __construct(?string $message = null, ?array $headers = null)
57 | {
58 | parent::__construct($this->status, $message ?? $this->message, null, $headers ?? $this->headers);
59 | }
60 |
61 | /**
62 | * Retrieve the HTTP status code,.
63 | *
64 | * @return int
65 | */
66 | public function statusCode(): int
67 | {
68 | return $this->status;
69 | }
70 |
71 | /**
72 | * Retrieve the error code.
73 | *
74 | * @return string|null
75 | */
76 | public function errorCode()
77 | {
78 | return $this->errorCode;
79 | }
80 |
81 | /**
82 | * Retrieve the error message.
83 | *
84 | * @return string|null
85 | */
86 | public function message()
87 | {
88 | return $this->message ?: null;
89 | }
90 |
91 | /**
92 | * Retrieve additional error data.
93 | *
94 | * @return array|null
95 | */
96 | public function data()
97 | {
98 | return $this->data;
99 | }
100 |
101 | /**
102 | * Retrieve attached headers.
103 | *
104 | * @return array|null
105 | */
106 | public function headers()
107 | {
108 | return $this->headers;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Exceptions/Http/PageNotFoundException.php:
--------------------------------------------------------------------------------
1 |
10 | * @license The MIT License
11 | */
12 | class PageNotFoundException extends HttpException
13 | {
14 | /**
15 | * An HTTP status code.
16 | *
17 | * @var int
18 | */
19 | protected $status = 404;
20 |
21 | /**
22 | * An error code.
23 | *
24 | * @var string|null
25 | */
26 | protected $errorCode = 'page_not_found';
27 | }
--------------------------------------------------------------------------------
/src/Exceptions/Http/RelationNotFoundException.php:
--------------------------------------------------------------------------------
1 |
10 | * @license The MIT License
11 | */
12 | class RelationNotFoundException extends HttpException
13 | {
14 | /**
15 | * An HTTP status code.
16 | *
17 | * @var int
18 | */
19 | protected $status = 422;
20 |
21 | /**
22 | * An error code.
23 | *
24 | * @var string|null
25 | */
26 | protected $errorCode = 'relation_not_found';
27 | }
--------------------------------------------------------------------------------
/src/Exceptions/Http/UnauthenticatedException.php:
--------------------------------------------------------------------------------
1 |
11 | * @license The MIT License
12 | */
13 | class UnauthenticatedException extends HttpException
14 | {
15 | /**
16 | * An HTTP status code.
17 | *
18 | * @var int
19 | */
20 | protected $status = 401;
21 |
22 | /**
23 | * The error code.
24 | *
25 | * @var string|null
26 | */
27 | protected $errorCode = 'unauthenticated';
28 | }
--------------------------------------------------------------------------------
/src/Exceptions/Http/UnauthorizedException.php:
--------------------------------------------------------------------------------
1 |
11 | * @license The MIT License
12 | */
13 | class UnauthorizedException extends HttpException
14 | {
15 | /**
16 | * An HTTP status code.
17 | *
18 | * @var int
19 | */
20 | protected $status = 401;
21 |
22 | /**
23 | * An error code.
24 | *
25 | * @var string|null
26 | */
27 | protected $errorCode = 'unauthorized';
28 | }
29 |
--------------------------------------------------------------------------------
/src/Exceptions/Http/ValidationFailedException.php:
--------------------------------------------------------------------------------
1 |
13 | * @license The MIT License
14 | */
15 | class ValidationFailedException extends HttpException
16 | {
17 | /**
18 | * An HTTP status code.
19 | *
20 | * @var int
21 | */
22 | protected $status = 422;
23 |
24 | /**
25 | * An error code.
26 | *
27 | * @var string|null
28 | */
29 | protected $errorCode = 'validation_failed';
30 |
31 | /**
32 | * A validator for fetching validation messages.
33 | *
34 | * @var \Illuminate\Contracts\Validation\Validator
35 | */
36 | protected $validator;
37 |
38 | /**
39 | * Construct the exception class.
40 | *
41 | * @param \Illuminate\Contracts\Validation\Validator $validator
42 | */
43 | public function __construct(Validator $validator)
44 | {
45 | $this->validator = $validator;
46 |
47 | parent::__construct();
48 | }
49 |
50 | /**
51 | * Retrieve the error data.
52 | *
53 | * @return array|null
54 | */
55 | public function data()
56 | {
57 | return ['fields' => $this->validator->getMessageBag()->toArray()];
58 | }
59 | }
--------------------------------------------------------------------------------
/src/Exceptions/InvalidErrorSerializerException.php:
--------------------------------------------------------------------------------
1 |
13 | * @license The MIT License
14 | */
15 | class InvalidErrorSerializerException extends RuntimeException
16 | {
17 | /**
18 | * Construct the exception class.
19 | */
20 | public function __construct()
21 | {
22 | parent::__construct('Serializer must be an instance of [' . ErrorSerializer::class . '].');
23 | }
24 | }
--------------------------------------------------------------------------------
/src/Exceptions/InvalidSuccessSerializerException.php:
--------------------------------------------------------------------------------
1 |
13 | * @license The MIT License
14 | */
15 | class InvalidSuccessSerializerException extends RuntimeException
16 | {
17 | /**
18 | * Construct the exception class.
19 | */
20 | public function __construct()
21 | {
22 | parent::__construct('Serializer must be an instance of [' . SerializerAbstract::class . '].');
23 | }
24 | }
--------------------------------------------------------------------------------
/src/Exceptions/InvalidTransformerException.php:
--------------------------------------------------------------------------------
1 |
13 | * @license The MIT License
14 | */
15 | class InvalidTransformerException extends RuntimeException
16 | {
17 | /**
18 | * Construct the exception class.
19 | */
20 | public function __construct()
21 | {
22 | parent::__construct('Transformer must be a callable or an instance of [' . Transformer::class . '].');
23 | }
24 | }
--------------------------------------------------------------------------------
/src/Facades/Responder.php:
--------------------------------------------------------------------------------
1 |
13 | * @license The MIT License
14 | *
15 | * @see \Flugg\Responder\Responder
16 | */
17 | class Responder extends Facade
18 | {
19 | /**
20 | * Get the registered name of the component.
21 | *
22 | * @return string
23 | */
24 | protected static function getFacadeAccessor()
25 | {
26 | return ResponderContract::class;
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Facades/Transformation.php:
--------------------------------------------------------------------------------
1 |
13 | * @license The MIT License
14 | *
15 | * @see \Flugg\Responder\Transformer
16 | */
17 | class Transformation extends Facade
18 | {
19 | /**
20 | * Get the registered name of the component.
21 | *
22 | * @return string
23 | */
24 | protected static function getFacadeAccessor()
25 | {
26 | return TransformationService::class;
27 | }
28 | }
--------------------------------------------------------------------------------
/src/FractalTransformFactory.php:
--------------------------------------------------------------------------------
1 |
16 | * @license The MIT License
17 | */
18 | class FractalTransformFactory implements TransformFactory
19 | {
20 | /**
21 | * A manager for executing transforms.
22 | *
23 | * @var \League\Fractal\Manager
24 | */
25 | protected $manager;
26 |
27 | /**
28 | * Construct the factory class.
29 | *
30 | * @param \League\Fractal\Manager $manager
31 | */
32 | public function __construct(Manager $manager)
33 | {
34 | $this->manager = $manager;
35 | }
36 |
37 | /**
38 | * Transform the given resource, and serialize the data with the given serializer.
39 | *
40 | * @param \League\Fractal\Resource\ResourceInterface $resource
41 | * @param \League\Fractal\Serializer\SerializerAbstract $serializer
42 | * @param array $options
43 | * @return array|null
44 | */
45 | public function make(ResourceInterface $resource, SerializerAbstract $serializer, array $options = [])
46 | {
47 | $options = $this->parseOptions($options, $resource);
48 |
49 | return $this->manager->setSerializer($serializer)
50 | ->parseIncludes($options['includes'])
51 | ->parseExcludes($options['excludes'])
52 | ->parseFieldsets($options['fieldsets'])
53 | ->createData($resource)
54 | ->toArray();
55 | }
56 |
57 | /**
58 | * Parse the transformation options.
59 | *
60 | * @param array $options
61 | * @param \League\Fractal\Resource\ResourceInterface $resource
62 | * @return array
63 | */
64 | protected function parseOptions(array $options, ResourceInterface $resource): array
65 | {
66 | $options = array_merge([
67 | 'includes' => [],
68 | 'excludes' => [],
69 | 'fieldsets' => [],
70 | ], $options);
71 |
72 | if (! empty($options['fieldsets'])) {
73 | if (empty($resourceKey = $resource->getResourceKey())) {
74 | throw new LogicException('Filtering fields using sparse fieldsets require resource key to be set.');
75 | }
76 |
77 | $options['fieldsets'] = $this->parseFieldsets($options['fieldsets'], $resourceKey, $options['includes']);
78 | }
79 |
80 | return $options;
81 | }
82 |
83 | /**
84 | * Parse the fieldsets for Fractal.
85 | *
86 | * @param array $fieldsets
87 | * @param string $resourceKey
88 | * @param array $includes
89 | * @return array
90 | */
91 | protected function parseFieldsets(array $fieldsets, string $resourceKey, array $includes): array
92 | {
93 | $includes = array_map(function ($include) use ($resourceKey) {
94 | return "$resourceKey.$include";
95 | }, $includes);
96 |
97 | foreach ($fieldsets as $key => $fields) {
98 | if (is_numeric($key)) {
99 | unset($fieldsets[$key]);
100 | $key = $resourceKey;
101 | }
102 |
103 | $fields = $this->parseFieldset($key, (array) $fields, $includes);
104 | $fieldsets[$key] = array_unique(array_merge(key_exists($key, $fieldsets) ? (array) $fieldsets[$key] : [], $fields));
105 | }
106 |
107 | return array_map(function ($fields) {
108 | return implode(',', $fields);
109 | }, $fieldsets);
110 | }
111 |
112 | /**
113 | * Parse the given fieldset and append any related resource keys.
114 | *
115 | * @param string $key
116 | * @param array $fields
117 | * @param array $includes
118 | * @return array
119 | */
120 | protected function parseFieldset(string $key, array $fields, array $includes): array
121 | {
122 | $childIncludes = array_reduce($includes, function ($segments, $include) use ($key) {
123 | return array_merge($segments, $this->resolveChildIncludes($key, $include));
124 | }, []);
125 |
126 | return array_merge($fields, array_unique($childIncludes));
127 | }
128 |
129 | /**
130 | * Resolve included segments that are a direct child to the given resource key.
131 | *
132 | * @param string $key
133 | * @param string $include
134 | * @return array
135 | */
136 | protected function resolveChildIncludes($key, string $include): array
137 | {
138 | if (count($segments = explode('.', $include)) <= 1) {
139 | return [];
140 | }
141 |
142 | $relation = $key === array_shift($segments) ? [$segments[0]] : [];
143 |
144 | return array_merge($relation, $this->resolveChildIncludes($key, implode('.', $segments)));
145 | }
146 | }
--------------------------------------------------------------------------------
/src/Http/MakesResponses.php:
--------------------------------------------------------------------------------
1 |
13 | * @license The MIT License
14 | */
15 | trait MakesResponses
16 | {
17 | /**
18 | * Build a successful response.
19 | *
20 | * @param mixed $data
21 | * @param callable|string|\Flugg\Responder\Transformers\Transformer|null $transformer
22 | * @param string|null $resourceKey
23 | * @return \Flugg\Responder\Http\Responses\SuccessResponseBuilder
24 | */
25 | public function success($data = null, $transformer = null, ?string $resourceKey = null): SuccessResponseBuilder
26 | {
27 | return app(Responder::class)->success(...func_get_args());
28 | }
29 |
30 | /**
31 | * Build an error response.
32 | *
33 | * @param mixed|null $errorCode
34 | * @param string|null $message
35 | * @return \Flugg\Responder\Http\Responses\ErrorResponseBuilder
36 | */
37 | public function error($errorCode = null, ?string $message = null): ErrorResponseBuilder
38 | {
39 | return app(Responder::class)->error(...func_get_args());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Http/Middleware/ConvertToSnakeCase.php:
--------------------------------------------------------------------------------
1 |
13 | * @license The MIT License
14 | */
15 | class ConvertToSnakeCase extends TransformsRequest
16 | {
17 | /**
18 | * A list of attributes that shouldn't be converted.
19 | *
20 | * @var array
21 | */
22 | protected $except = [
23 | //
24 | ];
25 |
26 | /**
27 | * Clean the data in the given array.
28 | *
29 | * @param array $data
30 | * @param string $keyPrefix
31 | * @return array
32 | */
33 | protected function cleanArray(array $data, $keyPrefix = '')
34 | {
35 | $parameters = [];
36 |
37 | foreach ($data as $key => $value) {
38 | $parameters[in_array($keyPrefix.$key, $this->except) ? $keyPrefix.$key : Str::snake($keyPrefix.$key)] = $value;
39 | }
40 |
41 | return $parameters;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Http/Responses/Decorators/EscapeHtmlDecorator.php:
--------------------------------------------------------------------------------
1 |
12 | * @license The MIT License
13 | */
14 | class EscapeHtmlDecorator extends ResponseDecorator
15 | {
16 | /**
17 | * Generate a JSON response.
18 | *
19 | * @param array $data
20 | * @param int $status
21 | * @param array $headers
22 | * @return \Illuminate\Http\JsonResponse
23 | */
24 | public function make(array $data, int $status, array $headers = []): JsonResponse
25 | {
26 | array_walk_recursive($data, function (&$value) {
27 | if (is_string($value)) {
28 | $value = e($value);
29 | }
30 | });
31 |
32 | return $this->factory->make($data, $status, $headers);
33 | }
34 | }
--------------------------------------------------------------------------------
/src/Http/Responses/Decorators/PrettyPrintDecorator.php:
--------------------------------------------------------------------------------
1 |
12 | * @license The MIT License
13 | */
14 | class PrettyPrintDecorator extends ResponseDecorator
15 | {
16 | /**
17 | * Generate a JSON response.
18 | *
19 | * @param array $data
20 | * @param int $status
21 | * @param array $headers
22 | * @return \Illuminate\Http\JsonResponse
23 | */
24 | public function make(array $data, int $status, array $headers = []): JsonResponse
25 | {
26 | $response = $this->factory->make($data, $status, $headers);
27 |
28 | $response->setEncodingOptions($response->getEncodingOptions() | JSON_PRETTY_PRINT);
29 |
30 | return $response;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Http/Responses/Decorators/ResponseDecorator.php:
--------------------------------------------------------------------------------
1 |
13 | * @license The MIT License
14 | */
15 | abstract class ResponseDecorator implements ResponseFactory
16 | {
17 | /**
18 | * The factory being decorated.
19 | *
20 | * @var \Flugg\Responder\Contracts\ResponseFactory
21 | */
22 | protected $factory;
23 |
24 | /**
25 | * Construct the decorator class.
26 | *
27 | * @param \Flugg\Responder\Contracts\ResponseFactory $factory
28 | */
29 | public function __construct(ResponseFactory $factory)
30 | {
31 | $this->factory = $factory;
32 | }
33 |
34 | /**
35 | * Generate a JSON response.
36 | *
37 | * @param array $data
38 | * @param int $status
39 | * @param array $headers
40 | * @return \Illuminate\Http\JsonResponse
41 | */
42 | abstract public function make(array $data, int $status, array $headers = []): JsonResponse;
43 | }
44 |
--------------------------------------------------------------------------------
/src/Http/Responses/Decorators/StatusCodeDecorator.php:
--------------------------------------------------------------------------------
1 |
12 | * @license The MIT License
13 | */
14 | class StatusCodeDecorator extends ResponseDecorator
15 | {
16 | /**
17 | * Generate a JSON response.
18 | *
19 | * @param array $data
20 | * @param int $status
21 | * @param array $headers
22 | * @return \Illuminate\Http\JsonResponse
23 | */
24 | public function make(array $data, int $status, array $headers = []): JsonResponse
25 | {
26 | return $this->factory->make(array_merge([
27 | 'status' => $status,
28 | ], $data), $status, $headers);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Http/Responses/Decorators/SuccessFlagDecorator.php:
--------------------------------------------------------------------------------
1 |
12 | * @license The MIT License
13 | */
14 | class SuccessFlagDecorator extends ResponseDecorator
15 | {
16 | /**
17 | * Generate a JSON response.
18 | *
19 | * @param array $data
20 | * @param int $status
21 | * @param array $headers
22 | * @return \Illuminate\Http\JsonResponse
23 | */
24 | public function make(array $data, int $status, array $headers = []): JsonResponse
25 | {
26 | return $this->factory->make(array_merge([
27 | 'success' => $status >= 100 && $status < 400,
28 | ], $data), $status, $headers);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Http/Responses/ErrorResponseBuilder.php:
--------------------------------------------------------------------------------
1 |
15 | * @license The MIT License
16 | */
17 | class ErrorResponseBuilder extends ResponseBuilder
18 | {
19 | /**
20 | * A factory for building error data output.
21 | *
22 | * @var \Flugg\Responder\Contracts\ErrorFactory
23 | */
24 | private $errorFactory;
25 |
26 | /**
27 | * A serializer for formatting error data.
28 | *
29 | * @var \Flugg\Responder\Contracts\ErrorSerializer
30 | */
31 | protected $serializer;
32 |
33 | /**
34 | * A code representing the error.
35 | *
36 | * @var string|null
37 | */
38 | protected $errorCode = null;
39 |
40 | /**
41 | * A message descibing the error.
42 | *
43 | * @var string|null
44 | */
45 | protected $message = null;
46 |
47 | /**
48 | * Additional data included with the error.
49 | *
50 | * @var array|null
51 | */
52 | protected $data = null;
53 |
54 | /**
55 | * A HTTP status code for the response.
56 | *
57 | * @var int
58 | */
59 | protected $status = 500;
60 |
61 | /**
62 | * Construct the builder class.
63 | *
64 | * @param \Flugg\Responder\Contracts\ResponseFactory $responseFactory
65 | * @param \Flugg\Responder\Contracts\ErrorFactory $errorFactory
66 | */
67 | public function __construct(ResponseFactory $responseFactory, ErrorFactory $errorFactory)
68 | {
69 | $this->errorFactory = $errorFactory;
70 |
71 | parent::__construct($responseFactory);
72 | }
73 |
74 | /**
75 | * Set the error code and message.
76 | *
77 | * @param mixed|null $errorCode
78 | * @param string|null $message
79 | * @return $this
80 | */
81 | public function error($errorCode = null, ?string $message = null)
82 | {
83 | $this->errorCode = $errorCode;
84 | $this->message = $message;
85 |
86 | return $this;
87 | }
88 |
89 | /**
90 | * Add additional data to the error.
91 | *
92 | * @param array|null $data
93 | * @return $this
94 | */
95 | public function data(?array $data = null)
96 | {
97 | $this->data = array_merge((array) $this->data, (array) $data);
98 |
99 | return $this;
100 | }
101 |
102 | /**
103 | * Set the error serializer.
104 | *
105 | * @param \Flugg\Responder\Contracts\ErrorSerializer|string $serializer
106 | * @return $this
107 | *
108 | * @throws \Flugg\Responder\Exceptions\InvalidErrorSerializerException
109 | */
110 | public function serializer($serializer)
111 | {
112 | if (is_string($serializer)) {
113 | $serializer = new $serializer;
114 | }
115 |
116 | if (! $serializer instanceof ErrorSerializer) {
117 | throw new InvalidErrorSerializerException;
118 | }
119 |
120 | $this->serializer = $serializer;
121 |
122 | return $this;
123 | }
124 |
125 | /**
126 | * Get the serialized response output.
127 | *
128 | * @return array
129 | */
130 | protected function getOutput(): array
131 | {
132 | return $this->errorFactory->make($this->serializer, $this->errorCode, $this->message, $this->data);
133 | }
134 |
135 | /**
136 | * Validate the HTTP status code for the response.
137 | *
138 | * @param int $status
139 | * @return void
140 | *
141 | * @throws \InvalidArgumentException
142 | */
143 | protected function validateStatusCode(int $status)
144 | {
145 | if ($status < 400 || $status >= 600) {
146 | throw new InvalidArgumentException("{$status} is not a valid error HTTP status code.");
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/Http/Responses/Factories/LaravelResponseFactory.php:
--------------------------------------------------------------------------------
1 |
14 | * @license The MIT License
15 | */
16 | class LaravelResponseFactory implements ResponseFactory
17 | {
18 | /**
19 | * The Laravel factory for making responses.
20 | *
21 | * @var \Illuminate\Contracts\Routing\ResponseFactory
22 | */
23 | protected $factory;
24 |
25 | /**
26 | * Construct the factory class.
27 | *
28 | * @param \Illuminate\Contracts\Routing\ResponseFactory $factory
29 | */
30 | public function __construct(BaseLaravelResponseFactory $factory)
31 | {
32 | $this->factory = $factory;
33 | }
34 |
35 | /**
36 | * Generate a JSON response.
37 | *
38 | * @param array $data
39 | * @param int $status
40 | * @param array $headers
41 | * @return \Illuminate\Http\JsonResponse
42 | */
43 | public function make(array $data, int $status, array $headers = []): JsonResponse
44 | {
45 | return $this->factory->json($data, $status, $headers);
46 | }
47 | }
--------------------------------------------------------------------------------
/src/Http/Responses/Factories/LumenResponseFactory.php:
--------------------------------------------------------------------------------
1 |
14 | * @license The MIT License
15 | */
16 | class LumenResponseFactory implements ResponseFactory
17 | {
18 | /**
19 | * The Lumen factory for making responses.
20 | *
21 | * @var \Laravel\Lumen\Http\ResponseFactory
22 | */
23 | protected $factory;
24 |
25 | /**
26 | * Construct the factory class.
27 | *
28 | * @param \Laravel\Lumen\Http\ResponseFactory $factory
29 | */
30 | public function __construct(BaseLumenResponseFactory $factory)
31 | {
32 | $this->factory = $factory;
33 | }
34 |
35 | /**
36 | * Generate a JSON response.
37 | *
38 | * @param array $data
39 | * @param int $status
40 | * @param array $headers
41 | * @return \Illuminate\Http\JsonResponse
42 | */
43 | public function make(array $data, int $status, array $headers = []): JsonResponse
44 | {
45 | return $this->factory->json($data, $status, $headers);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Http/Responses/ResponseBuilder.php:
--------------------------------------------------------------------------------
1 |
15 | * @license The MIT License
16 | */
17 | abstract class ResponseBuilder implements Arrayable, Jsonable
18 | {
19 | /**
20 | * A factory for making responses.
21 | *
22 | * @var \Flugg\Responder\Contracts\ResponseFactory
23 | */
24 | protected $responseFactory;
25 |
26 | /**
27 | * A HTTP status code for the response.
28 | *
29 | * @var int
30 | */
31 | protected $status;
32 |
33 | /**
34 | * Construct the builder class.
35 | *
36 | * @param \Flugg\Responder\Contracts\ResponseFactory $responseFactory
37 | */
38 | public function __construct(ResponseFactory $responseFactory)
39 | {
40 | $this->responseFactory = $responseFactory;
41 | }
42 |
43 | /**
44 | * Decorate the response with the given decorator.
45 | *
46 | * @param string[]|string $decorator
47 | * @return $this
48 | */
49 | public function decorator($decorator)
50 | {
51 | $decorators = is_array($decorator) ? $decorator : func_get_args();
52 |
53 | foreach ($decorators as $decorator) {
54 | $this->responseFactory = new $decorator($this->responseFactory);
55 | }
56 |
57 | return $this;
58 | }
59 |
60 | /**
61 | * Respond with an HTTP response.
62 | *
63 | * @param int|null $status
64 | * @param array $headers
65 | * @return \Illuminate\Http\JsonResponse
66 | */
67 | public function respond(?int $status = null, array $headers = []): JsonResponse
68 | {
69 | if (! is_null($status)) {
70 | $this->setStatusCode($status);
71 | }
72 |
73 | return $this->responseFactory->make($this->getOutput(), $this->status, $headers);
74 | }
75 |
76 | /**
77 | * Convert the response to an array.
78 | *
79 | * @return array
80 | */
81 | public function toArray(): array
82 | {
83 | return $this->respond()->getData(true);
84 | }
85 |
86 | /**
87 | * Convert the response to an Illuminate collection.
88 | *
89 | * @return \Illuminate\Support\Collection
90 | */
91 | public function toCollection(): Collection
92 | {
93 | return new Collection($this->toArray());
94 | }
95 |
96 | /**
97 | * Convert the response to JSON.
98 | *
99 | * @param int $options
100 | * @return string
101 | */
102 | public function toJson($options = 0): string
103 | {
104 | return json_encode($this->toArray(), $options);
105 | }
106 |
107 | /**
108 | * Set the HTTP status code for the response.
109 | *
110 | * @param int $status
111 | * @return void
112 | */
113 | protected function setStatusCode(int $status)
114 | {
115 | $this->validateStatusCode($this->status = $status);
116 | }
117 |
118 | /**
119 | * Get the serialized response output.
120 | *
121 | * @return array
122 | */
123 | abstract protected function getOutput(): array;
124 |
125 | /**
126 | * Convert the response to an array.
127 | *
128 | * @param int $status
129 | * @return void
130 | */
131 | abstract protected function validateStatusCode(int $status);
132 | }
133 |
--------------------------------------------------------------------------------
/src/Http/Responses/SuccessResponseBuilder.php:
--------------------------------------------------------------------------------
1 |
17 | * @license The MIT License
18 | *
19 | * @method $this meta(array $meta)
20 | * @method $this with(array | string $relations)
21 | * @method $this without(array | string $relations)
22 | * @method $this serializer(SerializerAbstract | string $serializer)
23 | * @method $this paginator(IlluminatePaginatorAdapter $paginator)
24 | * @method $this cursor(Cursor $cursor)
25 | */
26 | class SuccessResponseBuilder extends ResponseBuilder
27 | {
28 | /**
29 | * A builder for building transformed arrays.
30 | *
31 | * @var \Flugg\Responder\TransformBuilder
32 | */
33 | protected $transformBuilder;
34 |
35 | /**
36 | * A HTTP status code for the response.
37 | *
38 | * @var int
39 | */
40 | protected $status = 200;
41 |
42 | /**
43 | * Construct the builder class.
44 | *
45 | * @param \Flugg\Responder\Contracts\ResponseFactory $responseFactory
46 | * @param \Flugg\Responder\TransformBuilder $transformBuilder
47 | */
48 | public function __construct(ResponseFactory $responseFactory, TransformBuilder $transformBuilder)
49 | {
50 | $this->transformBuilder = $transformBuilder;
51 |
52 | parent::__construct($responseFactory);
53 | }
54 |
55 | /**
56 | * Set resource data for the transformation.
57 | *
58 | * @param mixed $data
59 | * @param \Flugg\Responder\Transformers\Transformer|callable|string|null $transformer
60 | * @param string|null $resourceKey
61 | * @return self
62 | */
63 | public function transform($data = null, $transformer = null, ?string $resourceKey = null): SuccessResponseBuilder
64 | {
65 | $this->transformBuilder->resource($data, $transformer, $resourceKey);
66 |
67 | return $this;
68 | }
69 |
70 | /**
71 | * Dynamically send calls to the transform builder.
72 | *
73 | * @param string $name
74 | * @param array $arguments
75 | * @return self|void
76 | */
77 | public function __call($name, $arguments)
78 | {
79 | if (in_array($name, ['cursor', 'paginator', 'meta', 'with', 'without', 'only', 'serializer'])) {
80 | $this->transformBuilder->$name(...$arguments);
81 |
82 | return $this;
83 | }
84 |
85 | throw new BadMethodCallException;
86 | }
87 |
88 | /**
89 | * Get the serialized response output.
90 | *
91 | * @return mixed
92 | */
93 | protected function getOutput(): array
94 | {
95 | return $this->transformBuilder->transform();
96 | }
97 |
98 | /**
99 | * Validate the HTTP status code for the response.
100 | *
101 | * @param int $status
102 | * @return void
103 | *
104 | * @throws \InvalidArgumentException
105 | */
106 | protected function validateStatusCode(int $status)
107 | {
108 | if ($status < 100 || $status >= 400) {
109 | throw new InvalidArgumentException("{$status} is not a valid success HTTP status code.");
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Pagination/CursorPaginator.php:
--------------------------------------------------------------------------------
1 |
14 | * @license The MIT License
15 | */
16 | class CursorPaginator
17 | {
18 | /**
19 | * A list of the items being paginated.
20 | *
21 | * @var \Illuminate\Support\Collection
22 | */
23 | protected $items;
24 |
25 | /**
26 | * The current cursor reference.
27 | *
28 | * @var int|string|null
29 | */
30 | protected $cursor;
31 |
32 | /**
33 | * The previous cursor reference.
34 | *
35 | * @var int|string|null
36 | */
37 | protected $previousCursor;
38 |
39 | /**
40 | * The next cursor reference.
41 | *
42 | * @var int|string|null
43 | */
44 | protected $nextCursor;
45 |
46 | /**
47 | * The current cursor resolver callback.
48 | *
49 | * @var \Closure|null
50 | */
51 | protected static $currentCursorResolver;
52 |
53 | /**
54 | * Create a new paginator instance.
55 | *
56 | * @param \Illuminate\Support\Collection|array|null $data
57 | * @param int|string|null $cursor
58 | * @param int|string|null $previousCursor
59 | * @param int|string|null $nextCursor
60 | */
61 | public function __construct($data, $cursor, $previousCursor, $nextCursor)
62 | {
63 | $this->cursor = $cursor;
64 | $this->previousCursor = $previousCursor;
65 | $this->nextCursor = $nextCursor;
66 |
67 | $this->set($data);
68 | }
69 |
70 | /**
71 | * Retrieve the current cursor reference.
72 | *
73 | * @return int|string|null
74 | */
75 | public function cursor()
76 | {
77 | return $this->cursor;
78 | }
79 |
80 | /**
81 | * Retireve the next cursor reference.
82 | *
83 | * @return int|string|null
84 | */
85 | public function previous()
86 | {
87 | return $this->previousCursor;
88 | }
89 |
90 | /**
91 | * Retireve the next cursor reference.
92 | *
93 | * @return int|string|null
94 | */
95 | public function next()
96 | {
97 | return $this->nextCursor;
98 | }
99 |
100 | /**
101 | * Get the slice of items being paginated.
102 | *
103 | * @return array
104 | */
105 | public function items(): array
106 | {
107 | return $this->items->all();
108 | }
109 |
110 | /**
111 | * Get the paginator's underlying collection.
112 | *
113 | * @return \Illuminate\Support\Collection
114 | */
115 | public function get(): Collection
116 | {
117 | return $this->items;
118 | }
119 |
120 | /**
121 | * Set the paginator's underlying collection.
122 | *
123 | * @param \Illuminate\Support\Collection|array|null $data
124 | * @return self
125 | */
126 | public function set($data): CursorPaginator
127 | {
128 | $this->items = $data instanceof Collection ? $data : collect($data);
129 |
130 | return $this;
131 | }
132 |
133 | /**
134 | * Resolve the current cursor using the cursor resolver.
135 | *
136 | * @param string $name
137 | * @return mixed
138 | * @throws \LogicException
139 | */
140 | public static function resolveCursor(string $name = 'cursor')
141 | {
142 | if (isset(static::$currentCursorResolver)) {
143 | return call_user_func(static::$currentCursorResolver, $name);
144 | }
145 |
146 | throw new LogicException("Could not resolve cursor with the name [{$name}].");
147 | }
148 |
149 | /**
150 | * Set the current cursor resolver callback.
151 | *
152 | * @param \Closure $resolver
153 | * @return void
154 | */
155 | public static function cursorResolver(Closure $resolver)
156 | {
157 | static::$currentCursorResolver = $resolver;
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/Pagination/PaginatorFactory.php:
--------------------------------------------------------------------------------
1 |
16 | * @license The MIT License
17 | */
18 | class PaginatorFactory implements PaginatorFactoryContract
19 | {
20 | /**
21 | * A list of query string values appended to the paginator links.
22 | *
23 | * @var array
24 | */
25 | protected $parameters;
26 |
27 | /**
28 | * Construct the factory class.
29 | *
30 | * @param array $parameters
31 | */
32 | public function __construct(array $parameters)
33 | {
34 | $this->parameters = $parameters;
35 | }
36 |
37 | /**
38 | * Make a Fractal paginator adapter from a Laravel paginator.
39 | *
40 | * @param \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator
41 | * @return \League\Fractal\Pagination\PaginatorInterface
42 | */
43 | public function make(LengthAwarePaginator $paginator): PaginatorInterface
44 | {
45 | $paginator->appends($this->parameters);
46 |
47 | return new IlluminatePaginatorAdapter($paginator);
48 | }
49 |
50 | /**
51 | * Make a Fractal paginator adapter from a Laravel paginator.
52 | *
53 | * @param \Flugg\Responder\Pagination\CursorPaginator $paginator
54 | * @return \League\Fractal\Pagination\Cursor
55 | */
56 | public function makeCursor(CursorPaginator $paginator): Cursor
57 | {
58 | return new Cursor($paginator->cursor(), $paginator->previous(), $paginator->next(), $paginator->get()->count());
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Resources/DataNormalizer.php:
--------------------------------------------------------------------------------
1 |
20 | * @license The MIT License
21 | */
22 | class DataNormalizer
23 | {
24 | /**
25 | * Normalize the data for a resource.
26 | *
27 | * @param mixed $data
28 | * @return mixed
29 | */
30 | public function normalize($data = null)
31 | {
32 | if ($this->isInstanceOf($data, [Builder::class, EloquentBuilder::class, CursorPaginator::class])) {
33 | return $data->get();
34 | } elseif ($data instanceof Paginator) {
35 | return $data->getCollection();
36 | } elseif ($data instanceof Relation) {
37 | return $this->normalizeRelation($data);
38 | }
39 |
40 | return $data;
41 | }
42 |
43 | /**
44 | * Normalize a relationship.
45 | *
46 | * @param \Illuminate\Database\Eloquent\Relations\Relation $relation
47 | * @return \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|null
48 | */
49 | protected function normalizeRelation(Relation $relation)
50 | {
51 | if ($this->isInstanceOf($relation, [BelongsTo::class, HasOne::class, MorphOne::class, MorphTo::class])) {
52 | return $relation->first();
53 | }
54 |
55 | return $relation->get();
56 | }
57 |
58 | /**
59 | * Indicates if the given data is an instance of any of the given class names.
60 | *
61 | * @param mixed $data
62 | * @param array $classes
63 | * @return bool
64 | */
65 | protected function isInstanceOf($data, array $classes): bool
66 | {
67 | foreach ($classes as $class) {
68 | if ($data instanceof $class) {
69 | return true;
70 | }
71 | }
72 |
73 | return false;
74 | }
75 | }
--------------------------------------------------------------------------------
/src/Resources/ResourceFactory.php:
--------------------------------------------------------------------------------
1 |
20 | * @license The MIT License
21 | */
22 | class ResourceFactory implements ResourceFactoryContract
23 | {
24 | /**
25 | * A service class, used to normalize data.
26 | *
27 | * @var \Flugg\Responder\Resources\DataNormalizer
28 | */
29 | protected $normalizer;
30 |
31 | /**
32 | * A resolver class, used to resolve resource keys.
33 | *
34 | * @var \Flugg\Responder\Contracts\Transformers\TransformerResolver
35 | */
36 | protected $transformerResolver;
37 |
38 | /**
39 | * A resolver class, used to resolve resource keys.
40 | *
41 | * @var \Flugg\Responder\Contracts\Resources\ResourceKeyResolver
42 | */
43 | protected $resourceKeyResolver;
44 |
45 | /**
46 | * Construct the factory class.
47 | *
48 | * @param \Flugg\Responder\Resources\DataNormalizer $normalizer
49 | * @param \Flugg\Responder\Contracts\Transformers\TransformerResolver $transformerResolver
50 | * @param \Flugg\Responder\Contracts\Resources\ResourceKeyResolver $resourceKeyResolver
51 | */
52 | public function __construct(DataNormalizer $normalizer, TransformerResolver $transformerResolver, ResourceKeyResolverContract $resourceKeyResolver)
53 | {
54 | $this->normalizer = $normalizer;
55 | $this->transformerResolver = $transformerResolver;
56 | $this->resourceKeyResolver = $resourceKeyResolver;
57 | }
58 |
59 | /**
60 | * Make resource from the given data.
61 | *
62 | * @param mixed $data
63 | * @param \Flugg\Responder\Transformers\Transformer|string|callable|null $transformer
64 | * @param string|null $resourceKey
65 | * @return \League\Fractal\Resource\ResourceInterface
66 | */
67 | public function make($data = null, $transformer = null, ?string $resourceKey = null): ResourceInterface
68 | {
69 | if ($data instanceof ResourceInterface) {
70 | return $this->makeFromResource($data, $transformer, $resourceKey);
71 | } elseif (is_null($data = $this->normalizer->normalize($data))) {
72 | return $this->instatiateResource($data, null, $resourceKey);
73 | }
74 |
75 | $transformer = $this->resolveTransformer($data, $transformer);
76 | $resourceKey = $this->resolveResourceKey($data, $resourceKey);
77 |
78 | return $this->instatiateResource($data, $transformer, $resourceKey);
79 | }
80 |
81 | /**
82 | * Make resource from the given resource.
83 | *
84 | * @param \League\Fractal\Resource\ResourceInterface $resource
85 | * @param \Flugg\Responder\Transformers\Transformer|string|callable|null $transformer
86 | * @param string|null $resourceKey
87 | * @return \League\Fractal\Resource\ResourceInterface
88 | */
89 | public function makeFromResource(ResourceInterface $resource, $transformer = null, ?string $resourceKey = null): ResourceInterface
90 | {
91 | $transformer = $this->resolveTransformer($resource->getData(), $transformer ?: $resource->getTransformer());
92 | $resourceKey = $this->resolveResourceKey($resource->getData(), $resourceKey ?: $resource->getResourceKey());
93 |
94 | return $resource->setTransformer($transformer)->setResourceKey($resourceKey);
95 | }
96 |
97 | /**
98 | * Instatiate a new resource instance.
99 | *
100 | * @param mixed $data
101 | * @param \Flugg\Responder\Transformers\Transformer|callable|null $transformer
102 | * @param string|null $resourceKey
103 | * @return \League\Fractal\Resource\ResourceInterface
104 | */
105 | protected function instatiateResource($data, $transformer = null, ?string $resourceKey = null): ResourceInterface
106 | {
107 | if (is_null($data)) {
108 | return new NullResource(null, null, $resourceKey);
109 | } elseif ($this->shouldCreateCollection($data)) {
110 | return new CollectionResource($data, $transformer, $resourceKey);
111 | } elseif (is_scalar($data)) {
112 | return new Primitive($data, $transformer, $resourceKey);
113 | }
114 |
115 | return new ItemResource($data, $transformer, $resourceKey);
116 | }
117 |
118 | /**
119 | * Indicates if the data belongs to a collection resource.
120 | *
121 | * @param mixed $data
122 | * @return bool
123 | */
124 | protected function shouldCreateCollection($data): bool
125 | {
126 | if (is_array($data)) {
127 | return ! Arr::isAssoc($data) && ! is_scalar(Arr::first($data));
128 | }
129 |
130 | return $data instanceof Traversable;
131 | }
132 |
133 | /**
134 | * Resolve a transformer.
135 | *
136 | * @param mixed $data
137 | * @param \Flugg\Responder\Transformers\Transformer|string|callable|null $transformer
138 | * @return \Flugg\Responder\Transformers\Transformer|callable
139 | */
140 | protected function resolveTransformer($data, $transformer)
141 | {
142 | if (isset($transformer)) {
143 | return $this->transformerResolver->resolve($transformer);
144 | }
145 |
146 | return $this->transformerResolver->resolveFromData($data);
147 | }
148 |
149 | /**
150 | * Resolve a resource key.
151 | *
152 | * @param mixed $data
153 | * @param string|null $resourceKey
154 | * @return null|string
155 | */
156 | protected function resolveResourceKey($data, ?string $resourceKey = null)
157 | {
158 | return ! empty($resourceKey) ? $resourceKey : $this->resourceKeyResolver->resolve($data);
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/Resources/ResourceKeyResolver.php:
--------------------------------------------------------------------------------
1 |
14 | * @license The MIT License
15 | */
16 | class ResourceKeyResolver implements ResourceKeyResolverContract
17 | {
18 | /**
19 | * Transformable to resource key mappings.
20 | *
21 | * @var array
22 | */
23 | protected $bindings = [];
24 |
25 | /**
26 | * Register a transformable to resource key binding.
27 | *
28 | * @param string|array $transformable
29 | * @param string $resourceKey
30 | * @return void
31 | */
32 | public function bind($transformable, string $resourceKey)
33 | {
34 | $this->bindings = array_merge($this->bindings, is_array($transformable) ? $transformable : [
35 | $transformable => $resourceKey,
36 | ]);
37 | }
38 |
39 | /**
40 | * Resolve a resource key from the given data.
41 | *
42 | * @param mixed $data
43 | * @return string
44 | */
45 | public function resolve($data)
46 | {
47 | $transformable = $this->resolveTransformableItem($data);
48 |
49 | if (is_object($transformable) && key_exists(get_class($transformable), $this->bindings)) {
50 | return $this->bindings[get_class($transformable)];
51 | }
52 |
53 | if ($transformable instanceof Model) {
54 | return $this->resolveFromModel($transformable);
55 | }
56 |
57 | return 'data';
58 | }
59 |
60 | /**
61 | * Resolve a resource key from the given model.
62 | *
63 | * @param \Illuminate\Database\Eloquent\Model $model
64 | * @return string
65 | */
66 | public function resolveFromModel(Model $model)
67 | {
68 | if (method_exists($model, 'getResourceKey')) {
69 | return $model->getResourceKey();
70 | }
71 |
72 | return $model->getTable();
73 | }
74 |
75 | /**
76 | * Resolve a transformable item from the given data.
77 | *
78 | * @param mixed $data
79 | * @return mixed
80 | */
81 | protected function resolveTransformableItem($data)
82 | {
83 | if (is_array($data) || $data instanceof Traversable) {
84 | foreach ($data as $item) {
85 | return $item;
86 | }
87 | }
88 |
89 | return $data;
90 | }
91 | }
--------------------------------------------------------------------------------
/src/Responder.php:
--------------------------------------------------------------------------------
1 |
13 | * @license The MIT License
14 | */
15 | class Responder implements ResponderContract
16 | {
17 | /**
18 | * A builder for building success responses.
19 | *
20 | * @var \Flugg\Responder\Http\Responses\SuccessResponseBuilder
21 | */
22 | protected $successResponseBuilder;
23 |
24 | /**
25 | * A builder for building error responses.
26 | *
27 | * @var \Flugg\Responder\Http\Responses\ErrorResponseBuilder
28 | */
29 | protected $errorResponseBuilder;
30 |
31 | /**
32 | * Construct the service class.
33 | *
34 | * @param \Flugg\Responder\Http\Responses\SuccessResponseBuilder $successResponseBuilder
35 | * @param \Flugg\Responder\Http\Responses\ErrorResponseBuilder $errorResponseBuilder
36 | */
37 | public function __construct(SuccessResponseBuilder $successResponseBuilder, ErrorResponseBuilder $errorResponseBuilder)
38 | {
39 | $this->successResponseBuilder = $successResponseBuilder;
40 | $this->errorResponseBuilder = $errorResponseBuilder;
41 | }
42 |
43 | /**
44 | * Build a successful response.
45 | *
46 | * @param mixed $data
47 | * @param callable|string|\Flugg\Responder\Transformers\Transformer|null $transformer
48 | * @param string|null $resourceKey
49 | * @return \Flugg\Responder\Http\Responses\SuccessResponseBuilder
50 | */
51 | public function success($data = null, $transformer = null, ?string $resourceKey = null): SuccessResponseBuilder
52 | {
53 | return $this->successResponseBuilder->transform($data, $transformer, $resourceKey);
54 | }
55 |
56 | /**
57 | * Build an error response.
58 | *
59 | * @param mixed|null $errorCode
60 | * @param string|null $message
61 | * @return \Flugg\Responder\Http\Responses\ErrorResponseBuilder
62 | */
63 | public function error($errorCode = null, ?string $message = null): ErrorResponseBuilder
64 | {
65 | return $this->errorResponseBuilder->error($errorCode, $message);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/ResponderServiceProvider.php:
--------------------------------------------------------------------------------
1 |
40 | * @license The MIT License
41 | */
42 | class ResponderServiceProvider extends BaseServiceProvider
43 | {
44 | /**
45 | * Indicates if loading of the provider is deferred.
46 | *
47 | * @var bool
48 | */
49 | protected $defer = false;
50 |
51 | /**
52 | * Register the service provider.
53 | *
54 | * @return void
55 | */
56 | public function register()
57 | {
58 | if ($this->app instanceof Laravel) {
59 | $this->registerLaravelBindings();
60 | } elseif ($this->app instanceof Lumen) {
61 | $this->registerLumenBindings();
62 | }
63 |
64 | $this->registerSerializerBindings();
65 | $this->registerErrorBindings();
66 | $this->registerFractalBindings();
67 | $this->registerTransformerBindings();
68 | $this->registerResourceBindings();
69 | $this->registerPaginationBindings();
70 | $this->registerTransformationBindings();
71 | $this->registerServiceBindings();
72 | }
73 |
74 | /**
75 | * Register Laravel bindings.
76 | *
77 | * @return void
78 | */
79 | protected function registerLaravelBindings()
80 | {
81 | $this->app->singleton(ResponseFactoryContract::class, function ($app) {
82 | return $this->decorateResponseFactory($app->make(LaravelResponseFactory::class));
83 | });
84 | }
85 |
86 | /**
87 | * Register Lumen bindings.
88 | *
89 | * @return void
90 | */
91 | protected function registerLumenBindings()
92 | {
93 | $this->app->singleton(ResponseFactoryContract::class, function ($app) {
94 | return $this->decorateResponseFactory($app->make(LumenResponseFactory::class));
95 | });
96 |
97 | $this->app->bind(Translator::class, function ($app) {
98 | return $app['translator'];
99 | });
100 | }
101 |
102 | /**
103 | * Decorate response factories.
104 | *
105 | * @param \Flugg\Responder\Contracts\ResponseFactory $factory
106 | * @return \Flugg\Responder\Contracts\ResponseFactory
107 | */
108 | protected function decorateResponseFactory(ResponseFactoryContract $factory): ResponseFactory
109 | {
110 | foreach ($this->app->config['responder.decorators'] as $decorator) {
111 | $factory = new $decorator($factory);
112 | };
113 |
114 | return $factory;
115 | }
116 |
117 | /**
118 | * Register serializer bindings.
119 | *
120 | * @return void
121 | */
122 | protected function registerSerializerBindings()
123 | {
124 | $this->app->bind(ErrorSerializerContract::class, function ($app) {
125 | return $app->make($app->config['responder.serializers.error']);
126 | });
127 |
128 | $this->app->bind(SerializerAbstract::class, function ($app) {
129 | return $app->make($app->config['responder.serializers.success']);
130 | });
131 | }
132 |
133 | /**
134 | * Register error bindings.
135 | *
136 | * @return void
137 | */
138 | protected function registerErrorBindings()
139 | {
140 | $this->app->singleton(ErrorMessageResolverContract::class, function ($app) {
141 | return $app->make(ErrorMessageResolver::class);
142 | });
143 |
144 | $this->app->singleton(ErrorFactoryContract::class, function ($app) {
145 | return $app->make(ErrorFactory::class);
146 | });
147 |
148 | $this->app->bind(ErrorResponseBuilder::class, function ($app) {
149 | return (new ErrorResponseBuilder($app->make(ResponseFactoryContract::class), $app->make(ErrorFactoryContract::class)))->serializer($app->make(ErrorSerializerContract::class));
150 | });
151 | }
152 |
153 | /**
154 | * Register Fractal bindings.
155 | *
156 | * @return void
157 | */
158 | protected function registerFractalBindings()
159 | {
160 | $this->app->bind(Manager::class, function ($app) {
161 | return (new Manager)->setRecursionLimit($app->config['responder.recursion_limit']);
162 | });
163 | }
164 |
165 | /**
166 | * Register transformer bindings.
167 | *
168 | * @return void
169 | */
170 | protected function registerTransformerBindings()
171 | {
172 | $this->app->singleton(TransformerResolverContract::class, function ($app) {
173 | return new TransformerResolver($app, $app->config['responder.fallback_transformer']);
174 | });
175 |
176 | BaseTransformer::containerResolver(function () {
177 | return $this->app->make(Container::class);
178 | });
179 | }
180 |
181 | /**
182 | * Register pagination bindings.
183 | *
184 | * @return void
185 | */
186 | protected function registerResourceBindings()
187 | {
188 | $this->app->singleton(ResourceKeyResolverContract::class, function ($app) {
189 | return $app->make(ResourceKeyResolver::class);
190 | });
191 |
192 | $this->app->singleton(ResourceFactoryContract::class, function ($app) {
193 | return $app->make(ResourceFactory::class);
194 | });
195 | }
196 |
197 | /**
198 | * Register pagination bindings.
199 | *
200 | * @return void
201 | */
202 | protected function registerPaginationBindings()
203 | {
204 | $this->app->bind(PaginatorFactoryContract::class, function ($app) {
205 | return new PaginatorFactory($app->make(Request::class)->query());
206 | });
207 | }
208 |
209 | /**
210 | * Register transformation bindings.
211 | *
212 | * @return void
213 | */
214 | protected function registerTransformationBindings()
215 | {
216 | $this->app->bind(TransformFactoryContract::class, function ($app) {
217 | return $app->make(FractalTransformFactory::class);
218 | });
219 |
220 | $this->app->bind(TransformBuilder::class, function ($app) {
221 | $request = $this->app->make(Request::class);
222 | $relations = $request->input($this->app->config['responder.load_relations_parameter'], []);
223 | $fieldsets = $request->input($app->config['responder.filter_fields_parameter'], []);
224 |
225 | return (new TransformBuilder($app->make(ResourceFactoryContract::class), $app->make(TransformFactoryContract::class), $app->make(PaginatorFactoryContract::class)))->serializer($app->make(SerializerAbstract::class))
226 | ->with(is_string($relations) ? explode(',', $relations) : $relations)
227 | ->only($fieldsets);
228 | });
229 | }
230 |
231 | /**
232 | * Register service bindings.
233 | *
234 | * @return void
235 | */
236 | protected function registerServiceBindings()
237 | {
238 | $this->app->bind(ResponderContract::class, function ($app) {
239 | return $app->make(Responder::class);
240 | });
241 | }
242 |
243 | /**
244 | * Bootstrap the application events.
245 | *
246 | * @return void
247 | */
248 | public function boot()
249 | {
250 | if ($this->app instanceof Laravel) {
251 | $this->bootLaravelApplication();
252 | } elseif ($this->app instanceof Lumen) {
253 | $this->bootLumenApplication();
254 | }
255 |
256 | $this->mergeConfigFrom(__DIR__ . '/../config/responder.php', 'responder');
257 | $this->commands(MakeTransformer::class);
258 | }
259 |
260 | /**
261 | * Bootstrap the Laravel application.
262 | *
263 | * @return void
264 | */
265 | protected function bootLaravelApplication()
266 | {
267 | if ($this->app->runningInConsole()) {
268 | $this->publishes([
269 | __DIR__ . '/../config/responder.php' => config_path('responder.php'),
270 | ], 'config');
271 | $this->publishes([
272 | __DIR__ . '/../resources/lang/en/errors.php' => base_path('resources/lang/en/errors.php'),
273 | ], 'lang');
274 | }
275 | }
276 |
277 | /**
278 | * Bootstrap the Lumen application.
279 | *
280 | * @return void
281 | */
282 | protected function bootLumenApplication()
283 | {
284 | $this->app->configure('responder');
285 | }
286 | }
--------------------------------------------------------------------------------
/src/Serializers/ErrorSerializer.php:
--------------------------------------------------------------------------------
1 |
11 | * @license The MIT License
12 | */
13 | class ErrorSerializer implements ErrorSerializerContract
14 | {
15 | /**
16 | * Format the error data.
17 | *
18 | * @param mixed|null $errorCode
19 | * @param string|null $message
20 | * @param array|null $data
21 | * @return array
22 | */
23 | public function format($errorCode = null, ?string $message = null, ?array $data = null): array
24 | {
25 | $response = [
26 | 'error' => [
27 | 'code' => $errorCode,
28 | 'message' => $message,
29 | ],
30 | ];
31 |
32 | if (is_array($data)) {
33 | $response['error'] = array_merge($response['error'], $data);
34 | }
35 |
36 | return $response;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Serializers/NoopSerializer.php:
--------------------------------------------------------------------------------
1 |
16 | * @license The MIT License
17 | */
18 | class NoopSerializer extends SuccessSerializer
19 | {
20 | /**
21 | * Serialize collection resources.
22 | *
23 | * @param string $resourceKey
24 | * @param array $data
25 | * @return array
26 | */
27 | public function collection($resourceKey, array $data): array
28 | {
29 | return $data;
30 | }
31 |
32 | /**
33 | * Serialize item resources.
34 | *
35 | * @param string $resourceKey
36 | * @param array $data
37 | * @return array
38 | */
39 | public function item($resourceKey, array $data): array
40 | {
41 | return $data;
42 | }
43 |
44 | /**
45 | * Serialize null resources.
46 | *
47 | * @return null|array
48 | */
49 | public function null(): ?array
50 | {
51 | return null;
52 | }
53 |
54 | /**
55 | * Format meta data.
56 | *
57 | * @param array $meta
58 | * @return array
59 | */
60 | public function meta(array $meta): array
61 | {
62 | return [];
63 | }
64 |
65 | /**
66 | * Format pagination data.
67 | *
68 | * @param \League\Fractal\Pagination\PaginatorInterface $paginator
69 | * @return array
70 | */
71 | public function paginator(PaginatorInterface $paginator): array
72 | {
73 | return [];
74 | }
75 |
76 | /**
77 | * Format cursor data.
78 | *
79 | * @param \League\Fractal\Pagination\CursorInterface $cursor
80 | * @return array
81 | */
82 | public function cursor(CursorInterface $cursor): array
83 | {
84 | return [];
85 | }
86 |
87 | /**
88 | * Merge includes into data.
89 | *
90 | * @param array $transformedData
91 | * @param array $includedData
92 | * @return array
93 | */
94 | public function mergeIncludes($transformedData, $includedData): array
95 | {
96 | return array_merge($transformedData, $includedData);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Serializers/SuccessSerializer.php:
--------------------------------------------------------------------------------
1 |
14 | * @license The MIT License
15 | */
16 | class SuccessSerializer extends ArraySerializer
17 | {
18 | /**
19 | * Serialize collection resources.
20 | *
21 | * @param string $resourceKey
22 | * @param array $data
23 | * @return array
24 | */
25 | public function collection($resourceKey, array $data): array
26 | {
27 | return ['data' => $data];
28 | }
29 |
30 | /**
31 | * Serialize item resources.
32 | *
33 | * @param string $resourceKey
34 | * @param array $data
35 | * @return array
36 | */
37 | public function item($resourceKey, array $data): array
38 | {
39 | return ['data' => $data];
40 | }
41 |
42 | /**
43 | * Serialize null resources.
44 | *
45 | * @return null|array
46 | */
47 | public function null(): ?array
48 | {
49 | return ['data' => null];
50 | }
51 |
52 | /**
53 | * Format meta data.
54 | *
55 | * @param array $meta
56 | * @return array
57 | */
58 | public function meta(array $meta): array
59 | {
60 | return $meta;
61 | }
62 |
63 | /**
64 | * Format pagination data.
65 | *
66 | * @param \League\Fractal\Pagination\PaginatorInterface $paginator
67 | * @return array
68 | */
69 | public function paginator(PaginatorInterface $paginator): array
70 | {
71 | $pagination = parent::paginator($paginator)['pagination'];
72 |
73 | return [
74 | 'pagination' => [
75 | 'count' => $pagination['count'],
76 | 'total' => $pagination['total'],
77 | 'perPage' => $pagination['per_page'],
78 | 'currentPage' => $pagination['current_page'],
79 | 'totalPages' => $pagination['total_pages'],
80 | 'links' => $pagination['links'],
81 | ],
82 | ];
83 | }
84 |
85 | /**
86 | * Format cursor data.
87 | *
88 | * @param \League\Fractal\Pagination\CursorInterface $cursor
89 | * @return array
90 | */
91 | public function cursor(CursorInterface $cursor): array
92 | {
93 | return [
94 | 'cursor' => [
95 | 'current' => $cursor->getCurrent(),
96 | 'previous' => $cursor->getPrev(),
97 | 'next' => $cursor->getNext(),
98 | 'count' => (int) $cursor->getCount(),
99 | ],
100 | ];
101 | }
102 |
103 | /**
104 | * Merge includes into data.
105 | *
106 | * @param array $transformedData
107 | * @param array $includedData
108 | * @return array
109 | */
110 | public function mergeIncludes($transformedData, $includedData): array
111 | {
112 | foreach (array_keys($includedData) as $key) {
113 | $includedData[$key] = $includedData[$key]['data'];
114 | }
115 |
116 | return array_merge($transformedData, $includedData);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/Testing/MakesApiRequests.php:
--------------------------------------------------------------------------------
1 |
12 | * @license The MIT License
13 | */
14 | trait MakesApiRequests
15 | {
16 | /**
17 | * Assert that the response is a valid success response.
18 | *
19 | * @param mixed $data
20 | * @param int $status
21 | * @return $this
22 | */
23 | protected function seeSuccess($data = null, $status = 200)
24 | {
25 | $response = $this->seeSuccessResponse($data, $status);
26 | $this->seeSuccessData($response->getData(true)['data']);
27 |
28 | return $this;
29 | }
30 |
31 | /**
32 | * Assert that the response is a valid success response.
33 | *
34 | * @param mixed $data
35 | * @param int $status
36 | * @return $this
37 | */
38 | protected function seeSuccessEquals($data = null, $status = 200)
39 | {
40 | $response = $this->seeSuccessResponse($data, $status);
41 | $this->seeJsonEquals($response->getData(true));
42 |
43 | return $this;
44 | }
45 |
46 | /**
47 | * Assert that the response data contains the given structure.
48 | *
49 | * @param mixed $data
50 | * @return $this
51 | */
52 | protected function seeSuccessStructure($data = null)
53 | {
54 | $this->seeJsonStructure([
55 | 'data' => $data,
56 | ]);
57 |
58 | return $this;
59 | }
60 |
61 | /**
62 | * Assert that the response is a valid success response.
63 | *
64 | * @param mixed $data
65 | * @param int $status
66 | * @return \Illuminate\Http\JsonResponse
67 | */
68 | protected function seeSuccessResponse($data = null, $status = 200): JsonResponse
69 | {
70 | $response = $this->app->make(Responder::class)->success($data, $status);
71 |
72 | $this->seeStatusCode($response->getStatusCode())->seeJson([
73 | 'success' => true,
74 | 'status' => $response->getStatusCode(),
75 | ])->seeJsonStructure(['data']);
76 |
77 | return $response;
78 | }
79 |
80 | /**
81 | * Assert that the response data contains given values.
82 | *
83 | * @param mixed $data
84 | * @return $this
85 | */
86 | protected function seeSuccessData($data = null)
87 | {
88 | collect($data)->each(function ($value, $key) {
89 | if (is_array($value)) {
90 | $this->seeSuccessData($value);
91 | } else {
92 | $this->seeJson([$key => $value]);
93 | }
94 | });
95 |
96 | return $this;
97 | }
98 |
99 | /**
100 | * Decodes JSON response and returns the data.
101 | *
102 | * @param string|array|null $attributes
103 | * @return array
104 | */
105 | protected function getSuccessData($attributes = null)
106 | {
107 | $rawData = $this->decodeResponseJson()['data'];
108 |
109 | if (is_null($attributes)) {
110 | return $rawData;
111 | } elseif (is_string($attributes)) {
112 | return array_get($rawData, $attributes);
113 | }
114 |
115 | $data = [];
116 |
117 | foreach ($attributes as $attribute) {
118 | $data[] = array_get($rawData, $attribute);
119 | }
120 |
121 | return $data;
122 | }
123 |
124 | /**
125 | * Assert that the response is a valid error response.
126 | *
127 | * @param string $error
128 | * @param int|null $status
129 | * @return $this
130 | */
131 | protected function seeError(string $error, ?int $status = null)
132 | {
133 | if (! is_null($status)) {
134 | $this->seeStatusCode($status);
135 | }
136 |
137 | if ($this->app->config->get('responder.status_code')) {
138 | $this->seeJson([
139 | 'status' => $status,
140 | ]);
141 | }
142 |
143 | return $this->seeJson([
144 | 'success' => false,
145 | ])->seeJsonSubset([
146 | 'error' => [
147 | 'code' => $error,
148 | ],
149 | ]);
150 | }
151 |
152 | /**
153 | * Asserts that the status code of the response matches the given code.
154 | *
155 | * @param int $status
156 | * @return $this
157 | */
158 | abstract protected function seeStatusCode($status);
159 |
160 | /**
161 | * Assert that the response contains JSON.
162 | *
163 | * @param array|null $data
164 | * @param bool $negate
165 | * @return $this
166 | */
167 | abstract public function seeJson(?array $data = null, $negate = false);
168 |
169 | /**
170 | * Assert that the JSON response has a given structure.
171 | *
172 | * @param array|null $structure
173 | * @param array|null $responseData
174 | * @return $this
175 | */
176 | abstract public function seeJsonStructure(?array $structure = null, $responseData = null);
177 |
178 | /**
179 | * Assert that the response is a superset of the given JSON.
180 | *
181 | * @param array $data
182 | * @return $this
183 | */
184 | abstract protected function seeJsonSubset(array $data);
185 |
186 | /**
187 | * Assert that the response contains an exact JSON array.
188 | *
189 | * @param array $data
190 | * @return $this
191 | */
192 | abstract public function seeJsonEquals(array $data);
193 |
194 | /**
195 | * Validate and return the decoded response JSON.
196 | *
197 | * @return array
198 | */
199 | abstract protected function decodeResponseJson();
200 | }
201 |
--------------------------------------------------------------------------------
/src/TransformBuilder.php:
--------------------------------------------------------------------------------
1 |
25 | * @license The MIT License
26 | */
27 | class TransformBuilder
28 | {
29 | /**
30 | * A factory class for making Fractal resources.
31 | *
32 | * @var \Flugg\Responder\Contracts\Resources\ResourceFactory
33 | */
34 | protected $resourceFactory;
35 |
36 | /**
37 | * A factory for making transformed arrays.
38 | *
39 | * @var \Flugg\Responder\Contracts\TransformFactory
40 | */
41 | private $transformFactory;
42 |
43 | /**
44 | * A factory used to build Fractal paginator adapters.
45 | *
46 | * @var \Flugg\Responder\Contracts\Pagination\PaginatorFactory
47 | */
48 | protected $paginatorFactory;
49 |
50 | /**
51 | * The resource that's being built.
52 | *
53 | * @var \League\Fractal\Resource\ResourceInterface
54 | */
55 | protected $resource;
56 |
57 | /**
58 | * A serializer for formatting data after transforming.
59 | *
60 | * @var \League\Fractal\Serializer\SerializerAbstract
61 | */
62 | protected $serializer;
63 |
64 | /**
65 | * A list of included relations.
66 | *
67 | * @var array
68 | */
69 | protected $with = [];
70 |
71 | /**
72 | * A list of excluded relations.
73 | *
74 | * @var array
75 | */
76 | protected $without = [];
77 |
78 | /**
79 | * A list of sparse fieldsets.
80 | *
81 | * @var array
82 | */
83 | protected $only = [];
84 |
85 | /**
86 | * Construct the builder class.
87 | *
88 | * @param \Flugg\Responder\Contracts\Resources\ResourceFactory $resourceFactory
89 | * @param \Flugg\Responder\Contracts\TransformFactory $transformFactory
90 | * @param \Flugg\Responder\Contracts\Pagination\PaginatorFactory $paginatorFactory
91 | */
92 | public function __construct(ResourceFactory $resourceFactory, TransformFactory $transformFactory, PaginatorFactory $paginatorFactory)
93 | {
94 | $this->resourceFactory = $resourceFactory;
95 | $this->transformFactory = $transformFactory;
96 | $this->paginatorFactory = $paginatorFactory;
97 | }
98 |
99 | /**
100 | * Make a resource from the given data and transformer and set the resource key.
101 | *
102 | * @param mixed $data
103 | * @param \Flugg\Responder\Transformers\Transformer|callable|string|null $transformer
104 | * @param string|null $resourceKey
105 | * @return $this
106 | */
107 | public function resource($data = null, $transformer = null, ?string $resourceKey = null)
108 | {
109 | $this->resource = $this->resourceFactory->make($data, $transformer, $resourceKey);
110 |
111 | if ($data instanceof CursorPaginator) {
112 | $this->cursor($this->paginatorFactory->makeCursor($data));
113 | } elseif ($data instanceof LengthAwarePaginator) {
114 | $this->paginator($this->paginatorFactory->make($data));
115 | }
116 |
117 | return $this;
118 | }
119 |
120 | /**
121 | * Manually set the cursor on the resource.
122 | *
123 | * @param \League\Fractal\Pagination\Cursor $cursor
124 | * @return $this
125 | */
126 | public function cursor(Cursor $cursor)
127 | {
128 | if ($this->resource instanceof CollectionResource) {
129 | $this->resource->setCursor($cursor);
130 | }
131 |
132 | return $this;
133 | }
134 |
135 | /**
136 | * Manually set the paginator on the resource.
137 | *
138 | * @param \League\Fractal\Pagination\IlluminatePaginatorAdapter $paginator
139 | * @return $this
140 | */
141 | public function paginator(IlluminatePaginatorAdapter $paginator)
142 | {
143 | if ($this->resource instanceof CollectionResource) {
144 | $this->resource->setPaginator($paginator);
145 | }
146 |
147 | return $this;
148 | }
149 |
150 | /**
151 | * Add meta data appended to the response data.
152 | *
153 | * @param array $data
154 | * @return $this
155 | */
156 | public function meta(array $data)
157 | {
158 | $this->resource->setMeta($data);
159 |
160 | return $this;
161 | }
162 |
163 | /**
164 | * Include relations to the transform.
165 | *
166 | * @param string[]|string $relations
167 | * @return $this
168 | */
169 | public function with($relations)
170 | {
171 | $relations = is_array($relations) ? $relations : func_get_args();
172 |
173 | foreach ($relations as $relation => $constraint) {
174 | if (is_numeric($relation)) {
175 | $relation = $constraint;
176 | $constraint = null;
177 | }
178 |
179 | $this->with = array_merge($this->with, [$relation => $constraint]);
180 | }
181 |
182 | return $this;
183 | }
184 |
185 | /**
186 | * Exclude relations from the transform.
187 | *
188 | * @param string[]|string $relations
189 | * @return $this
190 | */
191 | public function without($relations)
192 | {
193 | $this->without = array_merge($this->without, is_array($relations) ? $relations : func_get_args());
194 |
195 | return $this;
196 | }
197 |
198 | /**
199 | * Filter fields to output using sparse fieldsets.
200 | *
201 | * @param string[]|string $fields
202 | * @return $this
203 | */
204 | public function only($fields)
205 | {
206 | $this->only = array_merge($this->only, is_array($fields) ? $fields : func_get_args());
207 |
208 | return $this;
209 | }
210 |
211 | /**
212 | * Set the serializer.
213 | *
214 | * @param \League\Fractal\Serializer\SerializerAbstract|string $serializer
215 | * @return $this
216 | *
217 | * @throws \Flugg\Responder\Exceptions\InvalidSuccessSerializerException
218 | */
219 | public function serializer($serializer)
220 | {
221 | if (is_string($serializer)) {
222 | $serializer = new $serializer;
223 | }
224 |
225 | if (! $serializer instanceof SerializerAbstract) {
226 | throw new InvalidSuccessSerializerException;
227 | }
228 |
229 | $this->serializer = $serializer;
230 |
231 | return $this;
232 | }
233 |
234 | /**
235 | * Transform and serialize the data and return the transformed array.
236 | *
237 | * @return array|null
238 | */
239 | public function transform()
240 | {
241 | $this->prepareRelations($this->resource->getData(), $this->resource->getTransformer());
242 |
243 | return $this->transformFactory->make($this->resource ?: new NullResource, $this->serializer, [
244 | 'includes' => $this->with,
245 | 'excludes' => $this->without,
246 | 'fieldsets' => $this->only,
247 | ]);
248 | }
249 |
250 | /**
251 | * Prepare requested relations for the transformation.
252 | *
253 | * @param mixed $data
254 | * @param \Flugg\Responder\Transformers\Transformer|callable|string|null $transformer
255 | * @return void
256 | */
257 | protected function prepareRelations($data, $transformer)
258 | {
259 | if ($transformer instanceof Transformer) {
260 | $relations = $transformer->relations($this->with);
261 | $defaultRelations = $this->removeExcludedRelations($transformer->defaultRelations($this->with));
262 | $this->with = array_merge($relations, $defaultRelations);
263 | }
264 |
265 | if ($data instanceof Model || $data instanceof Collection) {
266 | $this->eagerLoadRelations($data, $this->with, $transformer);
267 | }
268 |
269 | $this->with = array_keys($this->with);
270 | }
271 |
272 | /**
273 | * Filter out relations that have been explicitly excluded using the [without] method.
274 | *
275 | * @param array $relations
276 | * @return array
277 | */
278 | protected function removeExcludedRelations(array $relations): array
279 | {
280 | return array_filter($relations, function ($relation) {
281 | return ! in_array($this->stripParametersFromRelation($relation), $this->without);
282 | }, ARRAY_FILTER_USE_KEY);
283 | }
284 |
285 | /**
286 | * Strip parameter suffix from the relation string by only taking what is in front of
287 | * the colon.
288 | *
289 | * @param string $relation
290 | * @return string
291 | */
292 | protected function stripParametersFromRelation(string $relation): string
293 | {
294 | return explode(':', $relation)[0];
295 | }
296 |
297 | /**
298 | * Eager load all requested relations except the ones defined as an "include" method
299 | * in the transformers. We also strip away any parameters from the relation name
300 | * and normalize relations by swapping "null" constraints to empty closures.
301 | *
302 | * @param mixed $data
303 | * @param array $requested
304 | * @param \Flugg\Responder\Transformers\Transformer|callable|string|null $transformer
305 | * @return void
306 | */
307 | protected function eagerLoadRelations($data, array $requested, $transformer)
308 | {
309 | $relations = collect(array_keys($requested))->reduce(function ($eagerLoads, $relation) use ($requested, $transformer) {
310 | $identifier = $this->stripParametersFromRelation($relation);
311 |
312 | if (config('responder.use_camel_case_relations')) {
313 | $identifier = Str::camel($identifier);
314 | }
315 |
316 | if (method_exists($transformer, 'include'.ucfirst($identifier))) {
317 | return $eagerLoads;
318 | }
319 |
320 | return array_merge($eagerLoads, [$identifier => $requested[$relation] ?: function () {
321 | }]);
322 | }, []);
323 |
324 | $data->loadMissing($relations);
325 | }
326 | }
327 |
--------------------------------------------------------------------------------
/src/Transformation.php:
--------------------------------------------------------------------------------
1 |
11 | * @license The MIT License
12 | */
13 | class Transformation
14 | {
15 | /**
16 | * A builder used to build transformed arrays.
17 | *
18 | * @var \Flugg\Responder\TransformBuilder
19 | */
20 | protected $transformBuilder;
21 |
22 | /**
23 | * Construct the service class.
24 | *
25 | * @param \Flugg\Responder\TransformBuilder $transformBuilder
26 | */
27 | public function __construct(TransformBuilder $transformBuilder)
28 | {
29 | $this->transformBuilder = $transformBuilder;
30 | }
31 |
32 | /**
33 | * Make a new transformation to transform data without serializing.
34 | *
35 | * @param mixed $data
36 | * @param \Flugg\Responder\Transformers\Transformer|callable|string|null $transformer
37 | * @param string|null $resourceKey
38 | * @return \Flugg\Responder\TransformBuilder
39 | */
40 | public function make($data = null, $transformer = null, ?string $resourceKey = null): TransformBuilder
41 | {
42 | return $this->transformBuilder->resource($data, $transformer, $resourceKey)->serializer(new NoopSerializer);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Transformers/ArrayTransformer.php:
--------------------------------------------------------------------------------
1 |
12 | * @license The MIT License
13 | */
14 | class ArrayTransformer extends Transformer
15 | {
16 | /**
17 | * Transform the data.
18 | *
19 | * @param mixed $data
20 | * @return array
21 | */
22 | public function transform($data)
23 | {
24 | return $data instanceof Arrayable ? $data->toArray() : $data;
25 | }
26 | }
--------------------------------------------------------------------------------
/src/Transformers/Concerns/HasRelationships.php:
--------------------------------------------------------------------------------
1 |
14 | * @license The MIT License
15 | */
16 | trait HasRelationships
17 | {
18 | /**
19 | * List of available relations.
20 | *
21 | * @var string[]
22 | */
23 | protected $relations = [];
24 |
25 | /**
26 | * A list of autoloaded default relations.
27 | *
28 | * @var array
29 | */
30 | protected $load = [];
31 |
32 | /**
33 | * Get a list of whitelisted relations that are requested, including nested relations.
34 | *
35 | * @param array $requested
36 | * @return array
37 | */
38 | public function relations(array $requested = []): array
39 | {
40 | $requested = $this->normalizeRelations($requested);
41 | $relations = $this->applyQueryConstraints($this->extractRelations($requested));
42 | $nestedRelations = $this->nestedRelations($requested, $relations, 'relations');
43 |
44 | return array_merge($relations, $nestedRelations);
45 | }
46 |
47 | /**
48 | * Get a list of default relations including nested relations.
49 | *
50 | * @param array $requested
51 | * @return array
52 | */
53 | public function defaultRelations(array $requested = []): array
54 | {
55 | $requested = $this->normalizeRelations($requested);
56 | $relations = $this->applyQueryConstraints($this->normalizeRelations($this->load));
57 | $nestedRelations = $this->nestedRelations($relations, array_merge($relations, $requested), 'defaultRelations');
58 |
59 | return array_merge($relations, $nestedRelations);
60 | }
61 |
62 | /**
63 | * Get a list of available relations from the transformer with a normalized structure.
64 | *
65 | * @return array
66 | */
67 | protected function availableRelations(): array
68 | {
69 | return $this->normalizeRelations(array_merge($this->relations, $this->load));
70 | }
71 |
72 | /**
73 | * Get nested relations from transformers resolved from the $available parameter that
74 | * also occur in the $requested parameter.
75 | *
76 | * @param array $requested
77 | * @param array $available
78 | * @param string $method
79 | * @return array
80 | */
81 | protected function nestedRelations(array $requested, array $available, string $method): array
82 | {
83 | $transformers = $this->mappedTransformers($available);
84 |
85 | return collect(array_keys($transformers))->reduce(function ($nestedRelations, $relation) use ($requested, $method, $transformers) {
86 | $transformer = $transformers[$relation];
87 | $children = $this->extractChildRelations($requested, $relation);
88 | $childRelations = $this->wrapChildRelations($transformer->$method($children), $relation);
89 |
90 | return array_merge($nestedRelations, $childRelations);
91 | }, []);
92 | }
93 |
94 | /**
95 | * Extract available root relations from the given list of relations.
96 | *
97 | * @param array $relations
98 | * @return array
99 | */
100 | protected function extractRelations(array $relations): array
101 | {
102 | $available = $this->availableRelations();
103 |
104 | return array_filter($this->mapRelations($relations, function ($relation, $constraint) {
105 | $identifier = explode('.', $relation)[0];
106 | $constraint = $identifier === $relation ? $constraint : null;
107 |
108 | return [$identifier => $constraint ?: $this->resolveQueryConstraint($identifier)];
109 | }), function ($relation) use ($available) {
110 | return Arr::has($available, explode(':', $relation)[0]);
111 | }, ARRAY_FILTER_USE_KEY);
112 | }
113 |
114 | /**
115 | * Extract all nested relations under a given identifier.
116 | *
117 | * @param array $relations
118 | * @param string $identifier
119 | * @return array
120 | */
121 | protected function extractChildRelations(array $relations, string $identifier): array
122 | {
123 | return array_reduce(array_keys($relations), function ($nested, $relation) use ($relations, $identifier) {
124 | if (! Str::startsWith($relation, "$identifier.")) {
125 | return $nested;
126 | }
127 |
128 | $nestedIdentifier = explode('.', $relation);
129 | array_shift($nestedIdentifier);
130 |
131 | return array_merge($nested, [implode('.', $nestedIdentifier) => $relations[$relation]]);
132 | }, []);
133 | }
134 |
135 | /**
136 | * Wrap the identifier of each relation of the given list of nested relations with
137 | * the parent relation identifier using dot notation.
138 | *
139 | * @param array $nestedRelations
140 | * @param string $relation
141 | * @return array
142 | */
143 | protected function wrapChildRelations(array $nestedRelations, string $relation): array
144 | {
145 | return $this->mapRelations($nestedRelations, function ($nestedRelation, $constraint) use ($relation) {
146 | return ["$relation.$nestedRelation" => $constraint];
147 | });
148 | }
149 |
150 | /**
151 | * Normalize relations to force an [identifier => constraint/transformer] structure.
152 | *
153 | * @param array $relations
154 | * @return array
155 | */
156 | protected function normalizeRelations(array $relations): array
157 | {
158 | return array_reduce(array_keys($relations), function ($normalized, $relation) use ($relations) {
159 | if (is_numeric($relation)) {
160 | return array_merge($normalized, [$relations[$relation] => null]);
161 | }
162 |
163 | return array_merge($normalized, [$relation => $relations[$relation]]);
164 | }, []);
165 | }
166 |
167 | /**
168 | * Map over a list of relations with the [identifier => constraint/transformer] structure.
169 | *
170 | * @param array $relations
171 | * @param callable $callback
172 | * @return array
173 | */
174 | protected function mapRelations(array $relations, callable $callback): array
175 | {
176 | $mapped = [];
177 |
178 | foreach ($relations as $identifier => $value) {
179 | $mapped = array_merge($mapped, $callback($identifier, $value));
180 | }
181 |
182 | return $mapped;
183 | }
184 |
185 | /**
186 | * Applies any query constraints defined in the transformer to the list of relaations.
187 | *
188 | * @param array $relations
189 | * @return array
190 | */
191 | protected function applyQueryConstraints(array $relations): array
192 | {
193 | return $this->mapRelations($relations, function ($relation, $constraint) {
194 | return [$relation => is_callable($constraint) ? $constraint : $this->resolveQueryConstraint($relation)];
195 | });
196 | }
197 |
198 | /**
199 | * Resolve a query constraint for a given relation identifier.
200 | *
201 | * @param string $identifier
202 | * @return \Closure|null
203 | */
204 | protected function resolveQueryConstraint(string $identifier)
205 | {
206 | if(config('responder.use_camel_case_relations')) {
207 | $identifier = Str::camel($identifier);
208 | }
209 |
210 | if (! method_exists($this, $method = 'load' . ucfirst($identifier))) {
211 | return null;
212 | }
213 |
214 | return function ($query) use ($method) {
215 | return $this->$method($query);
216 | };
217 | }
218 |
219 | /**
220 | * Resolve a relation from a model instance and an identifier.
221 | *
222 | * @param \Illuminate\Database\Eloquent\Model $model
223 | * @param string $identifier
224 | * @return mixed
225 | */
226 | protected function resolveRelation(Model $model, string $identifier)
227 | {
228 | if(config('responder.use_camel_case_relations')) {
229 | $identifier = Str::camel($identifier);
230 | }
231 |
232 | $relation = $model->$identifier;
233 |
234 | if (method_exists($this, $method = 'filter' . ucfirst($identifier))) {
235 | return $this->$method($relation);
236 | }
237 |
238 | return $relation;
239 | }
240 |
241 | /**
242 | * Resolve a list of transformers from a list of relations mapped to transformers.
243 | *
244 | * @param array $relations
245 | * @return array
246 | */
247 | protected function mappedTransformers(array $relations): array
248 | {
249 | $transformers = collect($this->availableRelations())->filter(function ($transformer) {
250 | return ! is_null($transformer);
251 | })->map(function ($transformer) {
252 | return $this->resolveTransformer($transformer);
253 | })->all();
254 |
255 | return array_intersect_key($transformers, $relations);
256 | }
257 |
258 | /**
259 | * Get a related transformer class mapped to a relation identifier.
260 | *
261 | * @param string $identifier
262 | * @return string|null
263 | */
264 | protected function mappedTransformerClass(string $identifier)
265 | {
266 | return $this->availableRelations()[$identifier] ?? null;
267 | }
268 |
269 | /**
270 | * Resolve a transformer from a class name string.
271 | *
272 | * @param string $transformer
273 | * @return mixed
274 | */
275 | protected abstract function resolveTransformer(string $transformer);
276 | }
--------------------------------------------------------------------------------
/src/Transformers/Concerns/MakesResources.php:
--------------------------------------------------------------------------------
1 |
17 | * @license The MIT License
18 | */
19 | trait MakesResources
20 | {
21 | /**
22 | * A list of cached related resources.
23 | *
24 | * @var \League\Fractal\ResourceInterface[]
25 | */
26 | protected $resources = [];
27 |
28 | /**
29 | * Make a resource.
30 | *
31 | * @param mixed $data
32 | * @param \Flugg\Responder\Transformers\Transformer|string|callable|null $transformer
33 | * @param string|null $resourceKey
34 | * @return \League\Fractal\Resource\ResourceInterface
35 | */
36 | protected function resource($data = null, $transformer = null, ?string $resourceKey = null): ResourceInterface
37 | {
38 | if ($data instanceof ResourceInterface) {
39 | return $data;
40 | }
41 |
42 | $resourceFactory = $this->resolveContainer()->make(ResourceFactory::class);
43 |
44 | return $resourceFactory->make($data, $transformer, $resourceKey);
45 | }
46 |
47 | /**
48 | * Include a related resource.
49 | *
50 | * @param string $identifier
51 | * @param mixed $data
52 | * @param array $parameters
53 | * @return \League\Fractal\Resource\ResourceInterface
54 | *
55 | * @throws \LogicException
56 | */
57 | protected function includeResource(string $identifier, $data, array $parameters): ResourceInterface
58 | {
59 | $transformer = $this->mappedTransformerClass($identifier);
60 |
61 | if (config('responder.use_camel_case_relations')) {
62 | $identifier = Str::camel($identifier);
63 | }
64 |
65 | if (method_exists($this, $method = 'include'.ucfirst($identifier))) {
66 | $resource = $this->resource($this->$method($data, collect($parameters)), $transformer, $identifier);
67 | } elseif ($data instanceof Model) {
68 | $resource = $this->includeResourceFromModel($data, $identifier, $transformer);
69 | } else {
70 | throw new LogicException('Relation ['.$identifier.'] not found in ['.get_class($this).'].');
71 | }
72 |
73 | return $resource;
74 | }
75 |
76 | /**
77 | * Include a related resource from a model and cache the resource type for following calls.
78 | *
79 | * @param \Illuminate\Database\Eloquent\Model $model
80 | * @param string $identifier
81 | * @param \Flugg\Responder\Transformers\Transformer|string|callable|null $transformer
82 | * @return \League\Fractal\Resource\ResourceInterface
83 | */
84 | protected function includeResourceFromModel(Model $model, string $identifier, $transformer = null): ResourceInterface
85 | {
86 | $data = $this->resolveRelation($model, $identifier);
87 |
88 | if (! $this->shouldCacheResource($data)) {
89 | return $this->resource($data, $transformer, $identifier);
90 | } elseif (key_exists($identifier, $this->resources)) {
91 | return $this->resources[$identifier]->setData($data);
92 | }
93 |
94 | return $this->resources[$identifier] = $this->resource($data, $transformer, $identifier);
95 | }
96 |
97 | /**
98 | * Indicates if the resource should be cached.
99 | *
100 | * @param mixed $data
101 | * @return bool
102 | */
103 | protected function shouldCacheResource($data): bool
104 | {
105 | return is_array($data) || $data instanceof Countable ? count($data) > 0 : is_null($data);
106 | }
107 |
108 | /**
109 | * Resolve a container using the resolver callback.
110 | *
111 | * @return \Illuminate\Contracts\Container\Container
112 | */
113 | abstract protected function resolveContainer(): Container;
114 |
115 | /**
116 | * Resolve relation data from a model.
117 | *
118 | * @param \Illuminate\Database\Eloquent\Model $model
119 | * @param string $identifier
120 | * @return mixed
121 | */
122 | abstract protected function resolveRelation(Model $model, string $identifier);
123 |
124 | /**
125 | * Get a related transformer class mapped to a relation identifier.
126 | *
127 | * @param string $identifier
128 | * @return string
129 | */
130 | abstract protected function mappedTransformerClass(string $identifier);
131 | }
132 |
--------------------------------------------------------------------------------
/src/Transformers/Concerns/OverridesFractal.php:
--------------------------------------------------------------------------------
1 |
13 | * @license The MIT License
14 | */
15 | trait OverridesFractal
16 | {
17 | /**
18 | * Overrides Fractal's getter for available includes.
19 | *
20 | * @return array
21 | */
22 | public function getAvailableIncludes(): array
23 | {
24 | if ($this->relations == ['*']) {
25 | return $this->resolveScopedIncludes($this->getCurrentScope());
26 | }
27 |
28 | return array_keys($this->normalizeRelations($this->relations));
29 | }
30 |
31 | /**
32 | * Overrides Fractal's getter for default includes.
33 | *
34 | * @return array
35 | */
36 | public function getDefaultIncludes(): array
37 | {
38 | return array_keys($this->normalizeRelations($this->load));
39 | }
40 |
41 | /**
42 | * Overrides Fractal's method for including a relation.
43 | *
44 | * @param \League\Fractal\Scope $scope
45 | * @param string $identifier
46 | * @param mixed $data
47 | * @return \League\Fractal\Resource\ResourceInterface
48 | */
49 | protected function callIncludeMethod(Scope $scope, $identifier, $data)
50 | {
51 | $parameters = iterator_to_array($scope->getManager()->getIncludeParams($scope->getIdentifier($identifier)));
52 |
53 | return $this->includeResource($identifier, $data, $parameters);
54 | }
55 |
56 | /**
57 | * Resolve scoped includes for the given scope.
58 | *
59 | * @param \League\Fractal\Scope $scope
60 | * @return array
61 | */
62 | protected function resolveScopedIncludes(Scope $scope): array
63 | {
64 | $level = count($scope->getParentScopes());
65 | $includes = $scope->getManager()->getRequestedIncludes();
66 |
67 | return collect($includes)->map(function ($include) {
68 | return explode('.', $include);
69 | })->filter(function ($include) use ($level) {
70 | return count($include) > $level;
71 | })->pluck($level)->unique()->all();
72 | }
73 |
74 | /**
75 | * Get the current scope of the transformer.
76 | *
77 | * @return \League\Fractal\Scope
78 | */
79 | public abstract function getCurrentScope();
80 |
81 | /**
82 | * Normalize relations to force a key value structure.
83 | *
84 | * @param array $relations
85 | * @return array
86 | */
87 | protected abstract function normalizeRelations(array $relations): array;
88 |
89 | /**
90 | * Include a related resource.
91 | *
92 | * @param string $identifier
93 | * @param mixed $data
94 | * @param array $parameters
95 | * @return \League\Fractal\Resource\ResourceInterface
96 | */
97 | protected abstract function includeResource(string $identifier, $data, array $parameters): ResourceInterface;
98 | }
--------------------------------------------------------------------------------
/src/Transformers/Transformer.php:
--------------------------------------------------------------------------------
1 |
18 | * @license The MIT License
19 | */
20 | abstract class Transformer extends TransformerAbstract
21 | {
22 | use HasRelationships;
23 | use MakesResources;
24 | use OverridesFractal;
25 |
26 | /**
27 | * The container resolver callback.
28 | *
29 | * @var \Closure|null
30 | */
31 | protected static $containerResolver;
32 |
33 | /**
34 | * Set a container using a resolver callback.
35 | *
36 | * @param \Closure $resolver
37 | * @return void
38 | */
39 | public static function containerResolver(Closure $resolver)
40 | {
41 | static::$containerResolver = $resolver;
42 | }
43 |
44 | /**
45 | * Resolve a container using the resolver callback.
46 | *
47 | * @return \Illuminate\Contracts\Container\Container
48 | */
49 | protected function resolveContainer(): Container
50 | {
51 | return call_user_func(static::$containerResolver);
52 | }
53 |
54 | /**
55 | * Resolve a transformer from a class name string.
56 | *
57 | * @param string $transformer
58 | * @return mixed
59 | */
60 | protected function resolveTransformer(string $transformer)
61 | {
62 | $transformerResolver = $this->resolveContainer()->make(TransformerResolver::class);
63 |
64 | return $transformerResolver->resolve($transformer);
65 | }
66 | }
--------------------------------------------------------------------------------
/src/Transformers/TransformerResolver.php:
--------------------------------------------------------------------------------
1 |
16 | * @license The MIT License
17 | */
18 | class TransformerResolver implements TransformerResolverContract
19 | {
20 | /**
21 | * Transformable to transformer mappings.
22 | *
23 | * @var array
24 | */
25 | protected $bindings = [];
26 |
27 | /**
28 | * A container used to resolve transformers.
29 | *
30 | * @var \Illuminate\Contracts\Container\Container
31 | */
32 | protected $container;
33 |
34 | /**
35 | * A fallback transformer to return when no transformer can be resolved.
36 | *
37 | * @var \Flugg\Responder\Transformers\Transformer|string|callable
38 | */
39 | protected $fallback;
40 |
41 | /**
42 | * Construct the resolver class.
43 | *
44 | * @param \Illuminate\Contracts\Container\Container $container
45 | * @param \Flugg\Responder\Transformers\Transformer|string|callable $fallback
46 | */
47 | public function __construct(Container $container, $fallback)
48 | {
49 | $this->container = $container;
50 | $this->fallback = $fallback;
51 | }
52 |
53 | /**
54 | * Register a transformable to transformer binding.
55 | *
56 | * @param string|array $transformable
57 | * @param string|callback|null $transformer
58 | * @return void
59 | */
60 | public function bind($transformable, $transformer = null)
61 | {
62 | $this->bindings = array_merge($this->bindings, is_array($transformable) ? $transformable : [
63 | $transformable => $transformer,
64 | ]);
65 | }
66 |
67 | /**
68 | * Resolve a transformer.
69 | *
70 | * @param \Flugg\Responder\Transformers\Transformer|string|callable $transformer
71 | * @return \Flugg\Responder\Transformers\Transformer|callable
72 | * @throws \Flugg\Responder\Exceptions\InvalidTransformerException
73 | */
74 | public function resolve($transformer)
75 | {
76 | if (is_string($transformer)) {
77 | return $this->container->make($transformer);
78 | }
79 |
80 | if (! is_callable($transformer) && ! $transformer instanceof Transformer) {
81 | throw new InvalidTransformerException;
82 | }
83 |
84 | return $transformer;
85 | }
86 |
87 | /**
88 | * Resolve a transformer from the given data.
89 | *
90 | * @param mixed $data
91 | * @return \Flugg\Responder\Transformers\Transformer|callable
92 | */
93 | public function resolveFromData($data)
94 | {
95 | $transformer = $this->resolveTransformer($this->resolveTransformableItem($data));
96 |
97 | return $this->resolve($transformer);
98 | }
99 |
100 | /**
101 | * Resolve a transformer from the transformable element.
102 | *
103 | * @param mixed $transformable
104 | * @return \Flugg\Responder\Contracts\Transformable|callable
105 | */
106 | protected function resolveTransformer($transformable)
107 | {
108 | if (is_object($transformable) && key_exists(get_class($transformable), $this->bindings)) {
109 | return $this->bindings[get_class($transformable)];
110 | }
111 |
112 | if ($transformable instanceof Transformable) {
113 | return $transformable->transformer();
114 | }
115 |
116 | return $this->resolve($this->fallback);
117 | }
118 |
119 | /**
120 | * Resolve a transformable item from the given data.
121 | *
122 | * @param mixed $data
123 | * @return mixed
124 | */
125 | protected function resolveTransformableItem($data)
126 | {
127 | if (is_array($data) || $data instanceof Traversable) {
128 | foreach ($data as $item) {
129 | return $item;
130 | }
131 | }
132 |
133 | return $data;
134 | }
135 | }
--------------------------------------------------------------------------------
/src/helpers.php:
--------------------------------------------------------------------------------
1 | make($data, $transformer);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------