1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Flugg\Responder; |
4
|
|
|
|
5
|
|
|
use Flugg\Responder\Contracts\Manager; |
6
|
|
|
use Flugg\Responder\Contracts\Responder as ResponderContract; |
7
|
|
|
use Flugg\Responder\Contracts\Transformable; |
8
|
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator; |
9
|
|
|
use Illuminate\Http\JsonResponse; |
10
|
|
|
use Illuminate\Support\Collection; |
11
|
|
|
use InvalidArgumentException; |
12
|
|
|
use League\Fractal\Pagination\IlluminatePaginatorAdapter; |
13
|
|
|
use League\Fractal\Resource\Collection as FractalCollection; |
14
|
|
|
use League\Fractal\Resource\Item as FractalItem; |
15
|
|
|
use League\Fractal\Resource\NullResource as FractalNull; |
16
|
|
|
use League\Fractal\Resource\ResourceInterface; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* The responder service. This class is responsible for generating JSON API responses. |
20
|
|
|
* It can also transform and serialize data using Fractal behind the scenes. |
21
|
|
|
* |
22
|
|
|
* @package Laravel Responder |
23
|
|
|
* @author Alexander Tømmerås <[email protected]> |
24
|
|
|
* @license The MIT License |
25
|
|
|
*/ |
26
|
|
|
class Responder implements ResponderContract |
27
|
|
|
{ |
28
|
|
|
/** |
29
|
|
|
* Generate a successful JSON response. |
30
|
|
|
* |
31
|
|
|
* @param mixed $data |
32
|
|
|
* @param int $statusCode |
33
|
|
|
* @param array|null $meta |
34
|
|
|
* @return JsonResponse |
35
|
|
|
*/ |
36
|
|
|
public function success( $data = null, $statusCode = 200, $meta = null ):JsonResponse |
37
|
|
|
{ |
38
|
|
|
if ( is_integer( $data ) ) { |
39
|
|
|
list( $data, $statusCode, $meta ) = [ null, $data, $statusCode ]; |
40
|
|
|
} elseif ( is_array( $statusCode ) ) { |
41
|
|
|
list( $statusCode, $meta ) = [ 200, $statusCode ]; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
$resource = $this->transform( $data ); |
45
|
|
|
|
46
|
|
|
if ( is_array( $meta ) ) { |
47
|
|
|
$resource->setMeta( $meta ); |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
$data = $this->serialize( $resource ); |
51
|
|
|
$data = $this->includeStatusCode( $statusCode, $data ); |
52
|
|
|
|
53
|
|
|
return response()->json( $data, $statusCode ); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Generate an unsuccessful JSON response. |
58
|
|
|
* |
59
|
|
|
* @param string $errorCode |
60
|
|
|
* @param int $statusCode |
61
|
|
|
* @param mixed $message |
62
|
|
|
* @return JsonResponse |
63
|
|
|
*/ |
64
|
|
|
public function error( string $errorCode, int $statusCode = 500, $message = null ):JsonResponse |
65
|
|
|
{ |
66
|
|
|
$response = $this->getErrorResponse( $errorCode, $statusCode ); |
67
|
|
|
$messages = $this->getErrorMessages( $errorCode, $message ); |
68
|
|
|
|
69
|
|
|
if ( count( $messages ) === 1 ) { |
70
|
|
|
$response[ 'error' ][ 'message' ] = $messages[ 0 ]; |
71
|
|
|
} else if ( count( $messages ) > 1 ) { |
72
|
|
|
$response[ 'error' ][ 'messages' ] = $messages; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
return response()->json( $response, $statusCode ); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Transforms the data. |
80
|
|
|
* |
81
|
|
|
* @param mixed $data |
82
|
|
|
* @param Transformer|null $transformer |
83
|
|
|
* @return ResourceInterface |
84
|
|
|
*/ |
85
|
|
|
public function transform( $data = null, Transformer $transformer = null ):ResourceInterface |
86
|
|
|
{ |
87
|
|
|
if ( is_null( $data ) ) { |
88
|
|
|
return new FractalNull(); |
89
|
|
|
} elseif ( $data instanceof Transformable ) { |
90
|
|
|
return $this->transformModel( $data, $transformer ); |
91
|
|
|
} elseif ( $data instanceof Collection ) { |
92
|
|
|
return $this->transformCollection( $data, $transformer ); |
93
|
|
|
} elseif ( $data instanceof Builder ) { |
|
|
|
|
94
|
|
|
return $this->transformCollection( $data->get(), $transformer ); |
95
|
|
|
} elseif ( $data instanceof LengthAwarePaginator ) { |
96
|
|
|
return $this->transformPaginator( $data, $transformer ); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
throw new InvalidArgumentException( 'Data must be one or multiple models implementing the Transformable contract.' ); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Serializes the data. |
104
|
|
|
* |
105
|
|
|
* @param ResourceInterface $resource |
106
|
|
|
* @return array |
107
|
|
|
*/ |
108
|
|
|
public function serialize( ResourceInterface $resource ):array |
109
|
|
|
{ |
110
|
|
|
$manager = app( Manager::class ); |
111
|
|
|
|
112
|
|
|
$data = $resource->getData(); |
113
|
|
|
$model = $data instanceof Collection ? $this->resolveModel( $data ) : $data; |
114
|
|
|
|
115
|
|
|
if ( ! is_null( $data ) ) { |
116
|
|
|
$transformer = $model::transformer(); |
117
|
|
|
$includes = ( new $transformer( $model ) )->getAvailableIncludes(); |
118
|
|
|
$manager = $manager->parseIncludes( $includes ); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
return $manager->createData( $resource )->toArray(); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Transform a transformable Eloquent model. |
126
|
|
|
* |
127
|
|
|
* @param Transformable $model |
128
|
|
|
* @param Transformer|null $transformer |
129
|
|
|
* @return ResourceInterface |
130
|
|
|
*/ |
131
|
|
View Code Duplication |
protected function transformModel( Transformable $model, Transformer $transformer = null ):ResourceInterface |
|
|
|
|
132
|
|
|
{ |
133
|
|
|
$transformer = $transformer ?: $model::transformer(); |
134
|
|
|
|
135
|
|
|
if ( is_null( $transformer ) ) { |
136
|
|
|
return new FractalNull(); |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
return $this->transformData( $model, new $transformer( $model ), $model->getTable() ); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Transform a collection of Eloquent models. |
144
|
|
|
* |
145
|
|
|
* @param Collection $collection |
146
|
|
|
* @param Transformer|null $transformer |
147
|
|
|
* @return ResourceInterface |
148
|
|
|
*/ |
149
|
|
View Code Duplication |
protected function transformCollection( Collection $collection, Transformer $transformer = null ):ResourceInterface |
|
|
|
|
150
|
|
|
{ |
151
|
|
|
$model = $this->resolveModel( $collection ); |
152
|
|
|
$transformer = $transformer ?: $model::transformer(); |
153
|
|
|
|
154
|
|
|
if ( is_null( $transformer ) ) { |
155
|
|
|
return new FractalNull(); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
return $this->transformData( $collection, new $transformer( $model ), $model->getTable() ); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Transform paginated data using Laravel's paginator. |
163
|
|
|
* |
164
|
|
|
* @param LengthAwarePaginator $paginator |
165
|
|
|
* @param Transformer|null $transformer |
166
|
|
|
* @return ResourceInterface |
167
|
|
|
*/ |
168
|
|
|
protected function transformPaginator( LengthAwarePaginator $paginator, Transformer $transformer = null ):ResourceInterface |
169
|
|
|
{ |
170
|
|
|
$resource = $this->transformCollection( $paginator->getCollection(), $transformer ); |
171
|
|
|
$resource->setPaginator( new IlluminatePaginatorAdapter( $paginator ) ); |
172
|
|
|
|
173
|
|
|
return $resource; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Transform the data using the given transformer. |
178
|
|
|
* |
179
|
|
|
* @param Transformable|Collection $data |
180
|
|
|
* @param Transformer|null $transformer |
181
|
|
|
* @param string $resourceKey |
182
|
|
|
* @return ResourceInterface |
183
|
|
|
*/ |
184
|
|
|
protected function transformData( $data, Transformer $transformer, string $resourceKey ):ResourceInterface |
185
|
|
|
{ |
186
|
|
|
$class = $data instanceof Transformable ? FractalItem::class : FractalCollection::class; |
187
|
|
|
$resource = new $class( $data, $transformer ); |
188
|
|
|
$resource->setResourceKey( $resourceKey ); |
189
|
|
|
|
190
|
|
|
return $resource; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* Resolves model class path from a collection of models. |
195
|
|
|
* |
196
|
|
|
* @param Collection $collection |
197
|
|
|
* @return Transformable |
198
|
|
|
* @throws InvalidArgumentException |
199
|
|
|
*/ |
200
|
|
|
protected function resolveModel( Collection $collection ):Transformable |
201
|
|
|
{ |
202
|
|
|
$class = $collection->first(); |
203
|
|
|
|
204
|
|
|
if ( ! $class instanceof Transformable ) { |
205
|
|
|
throw new InvalidArgumentException( 'Data must only contain models implementing the Transformable contract.' ); |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
$collection->each( function ( $model ) use ( $class ) { |
209
|
|
|
if ( get_class( $model ) !== get_class( $class ) ) { |
210
|
|
|
throw new InvalidArgumentException( 'You cannot transform arrays or collections with multiple model types.' ); |
211
|
|
|
} |
212
|
|
|
} ); |
213
|
|
|
|
214
|
|
|
return $class; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Here we prepend a status code to the response data, if status code is enabled in |
219
|
|
|
* the configuration file. |
220
|
|
|
* |
221
|
|
|
* @param int $statusCode |
222
|
|
|
* @param array $data |
223
|
|
|
* @return array |
224
|
|
|
*/ |
225
|
|
|
protected function includeStatusCode( int $statusCode, array $data ):array |
226
|
|
|
{ |
227
|
|
|
if ( ! config( 'responder.status_code' ) ) { |
228
|
|
|
return $data; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
return array_merge( [ |
232
|
|
|
'status' => $statusCode |
233
|
|
|
], $data ); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* Get the skeleton for an error response. |
238
|
|
|
* |
239
|
|
|
* @param string $errorCode |
240
|
|
|
* @param int $statusCode |
241
|
|
|
* @return array |
242
|
|
|
*/ |
243
|
|
|
protected function getErrorResponse( string $errorCode, int $statusCode ):array |
244
|
|
|
{ |
245
|
|
|
$response = [ |
246
|
|
|
'success' => false, |
247
|
|
|
'error' => [ |
248
|
|
|
'code' => $errorCode |
249
|
|
|
] |
250
|
|
|
]; |
251
|
|
|
|
252
|
|
|
return $this->includeStatusCode( $statusCode, $response ); |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* Get any error messages for the response. If no message can be found it will |
257
|
|
|
* try to resolve a set message from the translator. |
258
|
|
|
* |
259
|
|
|
* @param string $errorCode |
260
|
|
|
* @param mixed $message |
261
|
|
|
* @return array |
262
|
|
|
*/ |
263
|
|
|
protected function getErrorMessages( string $errorCode, $message ):array |
264
|
|
|
{ |
265
|
|
|
if ( is_array( $message ) ) { |
266
|
|
|
return $message; |
267
|
|
|
|
268
|
|
|
} elseif ( is_string( $message ) ) { |
269
|
|
|
if ( strlen( $message ) === 0 ) { |
270
|
|
|
return [ ]; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
return [ $message ]; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
return [ app( 'translator' )->trans( 'errors.' . $errorCode ) ]; |
277
|
|
|
} |
278
|
|
|
} |
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.