1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Flugg\Responder\Http; |
4
|
|
|
|
5
|
|
|
use Flugg\Responder\Contracts\Transformable; |
6
|
|
|
use Flugg\Responder\Exceptions\InvalidSerializerException; |
7
|
|
|
use Flugg\Responder\Exceptions\InvalidTransformerException; |
8
|
|
|
use Flugg\Responder\ResourceFactory; |
9
|
|
|
use Flugg\Responder\ResourceResolver; |
10
|
|
|
use Flugg\Responder\Transformation; |
11
|
|
|
use Flugg\Responder\Transformer; |
12
|
|
|
use Illuminate\Database\Eloquent\Model; |
13
|
|
|
use InvalidArgumentException; |
14
|
|
|
use League\Fractal\Manager; |
15
|
|
|
use League\Fractal\Resource\ResourceInterface; |
16
|
|
|
use League\Fractal\Serializer\SerializerAbstract; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* This class is a response builder for building successful JSON API responses and is |
20
|
|
|
* responsible for transforming and serializing the data. |
21
|
|
|
* |
22
|
|
|
* @package flugger/laravel-responder |
23
|
|
|
* @author Alexander Tømmerås <[email protected]> |
24
|
|
|
* @license The MIT License |
25
|
|
|
*/ |
26
|
|
|
class SuccessResponseBuilder extends ResponseBuilder |
27
|
|
|
{ |
28
|
|
|
/** |
29
|
|
|
* The manager responsible for transforming and serializing data. |
30
|
|
|
* |
31
|
|
|
* @var \League\Fractal\Manager |
32
|
|
|
*/ |
33
|
|
|
protected $manager; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* The meta data appended to the serialized data. |
37
|
|
|
* |
38
|
|
|
* @var array |
39
|
|
|
*/ |
40
|
|
|
protected $meta = []; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* The included relations. |
44
|
|
|
* |
45
|
|
|
* @var array |
46
|
|
|
*/ |
47
|
|
|
protected $relations = []; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* The Fractal resource instance containing the data and transformer. |
51
|
|
|
* |
52
|
|
|
* @var \League\Fractal\Resource\ResourceInterface |
53
|
|
|
*/ |
54
|
|
|
protected $resource; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* The resource factory used to generate resource instances. |
58
|
|
|
* |
59
|
|
|
* @var \Flugg\Responder\ResourceFactory |
60
|
|
|
*/ |
61
|
|
|
protected $resourceFactory; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* The HTTP status code for the response. |
65
|
|
|
* |
66
|
|
|
* @var int |
67
|
|
|
*/ |
68
|
|
|
protected $statusCode = 200; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* SuccessResponseBuilder constructor. |
72
|
|
|
* |
73
|
|
|
* @param \Illuminate\Contracts\Routing\ResponseFactory|\Laravel\Lumen\Http\ResponseFactory $responseFactory |
74
|
|
|
* @param \Flugg\Responder\ResourceFactory $resourceFactory |
75
|
|
|
* @param \League\Fractal\Manager $manager |
76
|
|
|
*/ |
77
|
|
|
public function __construct($responseFactory, ResourceFactory $resourceFactory, Manager $manager) |
78
|
|
|
{ |
79
|
|
|
$this->resourceFactory = $resourceFactory; |
80
|
|
|
$this->manager = $manager; |
81
|
|
|
$this->resource = $this->resourceFactory->make(); |
82
|
|
|
|
83
|
|
|
parent::__construct($responseFactory); |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* Add data to the meta data appended to the response data. |
88
|
|
|
* |
89
|
|
|
* @param array $data |
90
|
|
|
* @return self |
91
|
|
|
*/ |
92
|
|
|
public function addMeta(array $data):SuccessResponseBuilder |
93
|
|
|
{ |
94
|
|
|
$this->meta = array_merge($this->meta, $data); |
95
|
|
|
|
96
|
|
|
return $this; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Set the serializer used to serialize the resource data. |
101
|
|
|
* |
102
|
|
|
* @param array|string $relations |
103
|
|
|
* @return self |
104
|
|
|
*/ |
105
|
|
|
public function include($relations):SuccessResponseBuilder |
106
|
|
|
{ |
107
|
|
|
if (is_string($relations)) { |
108
|
|
|
$relations = explode(',', $relations); |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
$this->relations = array_merge($this->relations, (array) $relations); |
112
|
|
|
|
113
|
|
|
return $this; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Set the serializer used to serialize the resource data. |
118
|
|
|
* |
119
|
|
|
* @param \League\Fractal\Serializer\SerializerAbstract|string $serializer |
120
|
|
|
* @return self |
121
|
|
|
*/ |
122
|
|
|
public function serializer($serializer):SuccessResponseBuilder |
123
|
|
|
{ |
124
|
|
|
$this->manager->setSerializer($this->resolveSerializer($serializer)); |
125
|
|
|
|
126
|
|
|
return $this; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Set the HTTP status code for the response. |
131
|
|
|
* |
132
|
|
|
* @param int $statusCode |
133
|
|
|
* @return self |
134
|
|
|
* @throws \InvalidArgumentException |
135
|
|
|
*/ |
136
|
|
View Code Duplication |
public function setStatus(int $statusCode):ResponseBuilder |
|
|
|
|
137
|
|
|
{ |
138
|
|
|
if ($statusCode < 100 || $statusCode >= 400) { |
139
|
|
|
throw new InvalidArgumentException("{$statusCode} is not a valid success HTTP status code."); |
|
|
|
|
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
return parent::setStatus($statusCode); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Return response success flag as true |
147
|
|
|
* |
148
|
|
|
* @return bool |
149
|
|
|
*/ |
150
|
|
|
protected function isSuccessResponse():bool |
151
|
|
|
{ |
152
|
|
|
return true; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Set the transformation data. This will set a new resource instance on the response |
157
|
|
|
* builder depending on what type of data is provided. |
158
|
|
|
* |
159
|
|
|
* @param mixed|null $data |
160
|
|
|
* @param callable|string|null $transformer |
161
|
|
|
* @param string|null $resourceKey |
162
|
|
|
* @return self |
163
|
|
|
*/ |
164
|
|
|
public function transform($data = null, $transformer = null, string $resourceKey = null):SuccessResponseBuilder |
165
|
|
|
{ |
166
|
|
|
$resource = $this->resourceFactory->make($data); |
167
|
|
|
|
168
|
|
|
if (! is_null($resource->getData())) { |
169
|
|
|
$model = $this->resolveModel($resource->getData()); |
170
|
|
|
$transformer = $this->resolveTransformer($model, $transformer); |
171
|
|
|
$resourceKey = $this->resolveResourceKey($model, $resourceKey); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
if ($transformer instanceof Transformer) { |
175
|
|
|
$this->include($relations = $this->resolveNestedRelations($resource->getData())); |
176
|
|
|
|
177
|
|
|
if ($transformer->allRelationsAllowed()) { |
178
|
|
|
$transformer->setRelations($relations); |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$this->resource = $resource->setTransformer($transformer)->setResourceKey($resourceKey); |
|
|
|
|
183
|
|
|
|
184
|
|
|
return $this; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Convert the response to an array. |
189
|
|
|
* |
190
|
|
|
* @return array |
191
|
|
|
*/ |
192
|
|
|
public function toArray():array |
193
|
|
|
{ |
194
|
|
|
return $this->serialize($this->getResource()); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* Get the Fractal resource instance. |
199
|
|
|
* |
200
|
|
|
* @return \League\Fractal\Resource\ResourceInterface |
201
|
|
|
*/ |
202
|
|
|
public function getResource():ResourceInterface |
203
|
|
|
{ |
204
|
|
|
$this->manager->parseIncludes($this->relations); |
205
|
|
|
$transformer = $this->resource->getTransformer(); |
206
|
|
|
|
207
|
|
|
if ($transformer instanceof Transformer && $transformer->allRelationsAllowed()) { |
208
|
|
|
$this->resource->setTransformer($transformer->setRelations($this->manager->getRequestedIncludes())); |
|
|
|
|
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
return $this->resource->setMeta($this->meta); |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Get the Fractal manager responsible for transforming and serializing the data. |
216
|
|
|
* |
217
|
|
|
* @return \League\Fractal\Manager |
218
|
|
|
*/ |
219
|
|
|
public function getManager():Manager |
220
|
|
|
{ |
221
|
|
|
return $this->manager; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Resolve a serializer instance from the value. |
226
|
|
|
* |
227
|
|
|
* @param \League\Fractal\Serializer\SerializerAbstract|string $serializer |
228
|
|
|
* @return \League\Fractal\Serializer\SerializerAbstract |
229
|
|
|
* @throws \Flugg\Responder\Exceptions\InvalidSerializerException |
230
|
|
|
*/ |
231
|
|
|
protected function resolveSerializer($serializer):SerializerAbstract |
232
|
|
|
{ |
233
|
|
|
if (is_string($serializer)) { |
234
|
|
|
$serializer = new $serializer; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
if (! $serializer instanceof SerializerAbstract) { |
238
|
|
|
throw new InvalidSerializerException(); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
return $serializer; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Resolve a model instance from the data. |
246
|
|
|
* |
247
|
|
|
* @param \Illuminate\Database\Eloquent\Model|array $data |
248
|
|
|
* @return \Illuminate\Database\Eloquent\Model |
249
|
|
|
* @throws \InvalidArgumentException |
250
|
|
|
*/ |
251
|
|
|
protected function resolveModel($data):Model |
252
|
|
|
{ |
253
|
|
|
if ($data instanceof Model) { |
254
|
|
|
return $data; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
$model = array_values($data)[0]; |
258
|
|
|
if (! $model instanceof Model) { |
259
|
|
|
throw new InvalidArgumentException('You can only transform data containing Eloquent models.'); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
return $model; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Resolve a transformer. |
267
|
|
|
* |
268
|
|
|
* @param \Illuminate\Database\ELoquent\Model $model |
269
|
|
|
* @param \Flugg\Responder\Transformer|callable|null $transformer |
270
|
|
|
* @return \Flugg\Responder\Transformer|callable |
271
|
|
|
*/ |
272
|
|
|
protected function resolveTransformer(Model $model, $transformer = null) |
273
|
|
|
{ |
274
|
|
|
$transformer = $transformer ?: $this->resolveTransformerFromModel($model); |
275
|
|
|
|
276
|
|
|
if (is_string($transformer)) { |
277
|
|
|
$transformer = new $transformer; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
return $this->parseTransformer($transformer, $model); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* Resolve a transformer from the model. If the model is not transformable, a closure |
285
|
|
|
* based transformer will be created instead, from the model's fillable attributes. |
286
|
|
|
* |
287
|
|
|
* @param \Illuminate\Database\ELoquent\Model $model |
288
|
|
|
* @return \Flugg\Responder\Transformer|callable |
289
|
|
|
*/ |
290
|
|
|
protected function resolveTransformerFromModel(Model $model) |
291
|
|
|
{ |
292
|
|
|
if (! $model instanceof Transformable) { |
293
|
|
|
return function ($model) { |
294
|
|
|
return $model->toArray(); |
295
|
|
|
}; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
return $model::transformer(); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* Parse a transformer class and set relations. |
303
|
|
|
* |
304
|
|
|
* @param \Flugg\Responder\Transformer|callable $transformer |
305
|
|
|
* @param \Illuminate\Database\ELoquent\Model $model |
306
|
|
|
* @return \Flugg\Responder\Transformer|callable |
307
|
|
|
* @throws \InvalidTransformerException |
308
|
|
|
*/ |
309
|
|
|
protected function parseTransformer($transformer, Model $model) |
310
|
|
|
{ |
311
|
|
|
if ($transformer instanceof Transformer) { |
312
|
|
|
$relations = $transformer->allRelationsAllowed() ? $this->resolveRelations($model) : $transformer->getRelations(); |
313
|
|
|
$transformer = $transformer->setRelations($relations); |
314
|
|
|
|
315
|
|
|
} elseif (! is_callable($transformer)) { |
316
|
|
|
throw new InvalidTransformerException($model); |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
return $transformer; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
/** |
323
|
|
|
* Resolve eager loaded relations from the model. |
324
|
|
|
* |
325
|
|
|
* @param \Illuminate\Database\Eloquent\Model $model |
326
|
|
|
* @return array |
327
|
|
|
*/ |
328
|
|
|
protected function resolveRelations(Model $model):array |
329
|
|
|
{ |
330
|
|
|
return array_keys($model->getRelations()); |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
/** |
334
|
|
|
* Resolve eager loaded relations from the model including any nested relations. |
335
|
|
|
* |
336
|
|
|
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model $data |
337
|
|
|
* @return array |
338
|
|
|
*/ |
339
|
|
|
protected function resolveNestedRelations($data):array |
340
|
|
|
{ |
341
|
|
|
if (is_null($data)) { |
342
|
|
|
return []; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
$data = $data instanceof Model ? [$data] : $data; |
346
|
|
|
|
347
|
|
|
return collect($data)->flatMap(function ($model) { |
348
|
|
|
$relations = collect($model->getRelations()); |
349
|
|
|
|
350
|
|
|
return $relations->keys()->merge($relations->flatMap(function ($relation, $key) { |
351
|
|
|
return collect($this->resolveNestedRelations($relation))->map(function ($nestedRelation) use ($key) { |
352
|
|
|
return $key . '.' . $nestedRelation; |
353
|
|
|
}); |
354
|
|
|
})); |
355
|
|
|
})->unique()->toArray(); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* Resolve the resource key from the model. |
360
|
|
|
* |
361
|
|
|
* @param \Illuminate\Database\Eloquent\Model $model |
362
|
|
|
* @param string|null $resourceKey |
363
|
|
|
* @return string |
364
|
|
|
*/ |
365
|
|
|
protected function resolveResourceKey(Model $model, string $resourceKey = null):string |
366
|
|
|
{ |
367
|
|
|
if (! is_null($resourceKey)) { |
368
|
|
|
return $resourceKey; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
if (method_exists($model, 'getResourceKey')) { |
372
|
|
|
return $model->getResourceKey(); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
return $model->getTable(); |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
/** |
379
|
|
|
* Serialize the transformation data. |
380
|
|
|
* |
381
|
|
|
* @param ResourceInterface $resource |
382
|
|
|
* @return array |
383
|
|
|
*/ |
384
|
|
|
protected function serialize(ResourceInterface $resource):array |
385
|
|
|
{ |
386
|
|
|
return $this->manager->createData($resource)->toArray(); |
387
|
|
|
} |
388
|
|
|
} |
|
|
|
|
389
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.