1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Flugg\Responder\Factories; |
4
|
|
|
|
5
|
|
|
use Flugg\Responder\Contracts\Manager; |
6
|
|
|
use Flugg\Responder\Contracts\Transformable; |
7
|
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator as Paginator; |
8
|
|
|
use Illuminate\Database\Eloquent\Builder; |
9
|
|
|
use Illuminate\Database\Eloquent\Model; |
10
|
|
|
use Illuminate\Database\Eloquent\Relations\Pivot; |
11
|
|
|
use Illuminate\Http\JsonResponse; |
12
|
|
|
use Illuminate\Support\Collection; |
13
|
|
|
use InvalidArgumentException; |
14
|
|
|
use League\Fractal\Pagination\IlluminatePaginatorAdapter; |
15
|
|
|
use League\Fractal\Resource\Collection as FractalCollection; |
16
|
|
|
use League\Fractal\Resource\Item as FractalItem; |
17
|
|
|
use League\Fractal\Resource\NullResource as FractalNull; |
18
|
|
|
use League\Fractal\Resource\ResourceInterface; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* |
22
|
|
|
* |
23
|
|
|
* @package Laravel Responder |
24
|
|
|
* @author Alexander Tømmerås <[email protected]> |
25
|
|
|
* @license The MIT License |
26
|
|
|
*/ |
27
|
|
|
class SuccessResponseFactory extends ResponseFactory |
28
|
|
|
{ |
29
|
|
|
/** |
30
|
|
|
* Mapping between the type of data and which transform method should be used. |
31
|
|
|
* |
32
|
|
|
* @var array |
33
|
|
|
*/ |
34
|
|
|
protected $transforms = [ |
35
|
|
|
Transformable::class => 'transformModel', |
36
|
|
|
Collection::class => 'transformCollection', |
37
|
|
|
Builder::class => 'transformBuilder', |
38
|
|
|
Paginator::class => 'transformPaginator', |
39
|
|
|
Pivot::class => 'transformPivot' |
40
|
|
|
]; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Generate a successful JSON response. |
44
|
|
|
* |
45
|
|
|
* @param mixed $data |
46
|
|
|
* @param int $statusCode |
47
|
|
|
* @param array $meta |
48
|
|
|
* @return JsonResponse |
49
|
|
|
*/ |
50
|
|
|
public function make( $data = null, $statusCode = 200, $meta = [ ] ):JsonResponse |
51
|
|
|
{ |
52
|
|
|
$resource = $this->transform( $data ); |
53
|
|
|
$resource->setMeta( $meta ); |
54
|
|
|
|
55
|
|
|
$data = $this->serialize( $resource ); |
56
|
|
|
$data = $this->includeStatusCode( $statusCode, $data ); |
57
|
|
|
|
58
|
|
|
return parent::make( $data, $statusCode ); |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Transforms the data. |
63
|
|
|
* |
64
|
|
|
* @param mixed $data |
65
|
|
|
* @param mixed $transformer |
66
|
|
|
* @return ResourceInterface |
67
|
|
|
*/ |
68
|
|
|
public function transform( $data = null, $transformer = null ):ResourceInterface |
69
|
|
|
{ |
70
|
|
|
if ( is_null( $data ) ) { |
71
|
|
|
return new FractalNull(); |
72
|
|
|
}; |
73
|
|
|
|
74
|
|
|
foreach ( $this->transforms as $class => $transform ) { |
75
|
|
|
if ( $data instanceof $class ) { |
76
|
|
|
return $this->$transform( $data, $transformer ); |
77
|
|
|
} |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
throw new InvalidArgumentException( 'Data must be one or multiple models implementing the Transformable contract.' ); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Transform a transformable Eloquent model. |
85
|
|
|
* |
86
|
|
|
* @param Model $model |
87
|
|
|
* @param mixed $transformer |
88
|
|
|
* @return FractalItem |
89
|
|
|
*/ |
90
|
|
View Code Duplication |
protected function transformModel( Model $model, $transformer = null ):FractalItem |
|
|
|
|
91
|
|
|
{ |
92
|
|
|
$transformer = $transformer ?: $model::transformer(); |
93
|
|
|
|
94
|
|
|
if ( is_null( $transformer ) ) { |
95
|
|
|
return new FractalItem( $model, function () use ( $model ) { |
96
|
|
|
return $model->toArray(); |
97
|
|
|
} ); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
return $this->transformData( $model, new $transformer( $model ), $model->getTable() ); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Transform a collection of Eloquent models. |
105
|
|
|
* |
106
|
|
|
* @param Collection $collection |
107
|
|
|
* @param mixed $transformer |
108
|
|
|
* @return FractalCollection |
109
|
|
|
*/ |
110
|
|
View Code Duplication |
protected function transformCollection( Collection $collection, $transformer = null ):FractalCollection |
|
|
|
|
111
|
|
|
{ |
112
|
|
|
$model = $this->resolveModel( $collection ); |
113
|
|
|
$transformer = $transformer ?: $model::transformer(); |
114
|
|
|
|
115
|
|
|
if ( is_null( $transformer ) ) { |
116
|
|
|
return new FractalCollection( $collection, function () use ( $collection ) { |
117
|
|
|
return $collection->toArray(); |
118
|
|
|
} ); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
return $this->transformData( $collection, new $transformer( $model ), $model->getTable() ); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Transform an Eloquent builder. |
126
|
|
|
* |
127
|
|
|
* @param Builder $query |
128
|
|
|
* @param mixed $transformer |
129
|
|
|
* @return FractalCollection |
130
|
|
|
*/ |
131
|
|
|
protected function transformBuilder( Builder $query, $transformer = null ):FractalCollection |
132
|
|
|
{ |
133
|
|
|
return $this->transformCollection( $query->get(), $transformer ); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Transform paginated data using Laravel's paginator. |
138
|
|
|
* |
139
|
|
|
* @param Paginator $paginator |
140
|
|
|
* @param mixed $transformer |
141
|
|
|
* @return FractalCollection |
142
|
|
|
*/ |
143
|
|
|
protected function transformPaginator( Paginator $paginator, $transformer = null ):FractalCollection |
144
|
|
|
{ |
145
|
|
|
$resource = $this->transformCollection( $paginator->getCollection(), $transformer ); |
146
|
|
|
$resource->setPaginator( new IlluminatePaginatorAdapter( $paginator ) ); |
147
|
|
|
|
148
|
|
|
return $resource; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Transform paginated data using Laravel's paginator. |
153
|
|
|
* |
154
|
|
|
* @param Pivot $pivot |
155
|
|
|
* @param mixed $transformer |
156
|
|
|
* @return ResourceInterface |
157
|
|
|
*/ |
158
|
|
|
protected function transformPivot( Pivot $pivot, $transformer = null ):ResourceInterface |
159
|
|
|
{ |
160
|
|
|
return $this->transformData( $pivot, $transformer ); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Transform the data using the given transformer. |
165
|
|
|
* |
166
|
|
|
* @param Transformable|Collection $data |
167
|
|
|
* @param mixed $transformer |
168
|
|
|
* @param string|null $resourceKey |
169
|
|
|
* @return ResourceInterface |
170
|
|
|
*/ |
171
|
|
|
protected function transformData( $data, $transformer, string $resourceKey = null ):ResourceInterface |
172
|
|
|
{ |
173
|
|
|
$class = $data instanceof Model ? FractalItem::class : FractalCollection::class; |
174
|
|
|
$resource = new $class( $data, $transformer ); |
175
|
|
|
|
176
|
|
|
if ( is_string( $resourceKey ) ) { |
177
|
|
|
$resource->setResourceKey( $resourceKey ); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
return $resource; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* Serializes the data. |
185
|
|
|
* |
186
|
|
|
* @param ResourceInterface $resource |
187
|
|
|
* @return array |
188
|
|
|
*/ |
189
|
|
|
protected function serialize( ResourceInterface $resource ):array |
190
|
|
|
{ |
191
|
|
|
$manager = app( Manager::class ); |
192
|
|
|
|
193
|
|
|
$data = $resource->getData(); |
194
|
|
|
$model = $data instanceof Collection ? $this->resolveModel( $data ) : $data; |
195
|
|
|
|
196
|
|
|
if ( ! is_null( $data ) ) { |
197
|
|
|
$transformer = $model::transformer(); |
198
|
|
|
$includes = is_string( $transformer ) ? ( new $transformer( $model ) )->getAvailableIncludes() : [ ]; |
199
|
|
|
$manager = $manager->parseIncludes( $includes ); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
return $manager->createData( $resource )->toArray(); |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Here we prepend a status code to the response data, if status code is enabled in |
207
|
|
|
* the configuration file. |
208
|
|
|
* |
209
|
|
|
* @param int $statusCode |
210
|
|
|
* @param array $data |
211
|
|
|
* @return array |
212
|
|
|
*/ |
213
|
|
|
protected function includeStatusCode( int $statusCode, array $data ):array |
214
|
|
|
{ |
215
|
|
|
if ( ! $this->includeStatusCode ) { |
216
|
|
|
return $data; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
return array_merge( [ |
220
|
|
|
'status' => $statusCode |
221
|
|
|
], $data ); |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Resolves model class path from a collection of models. |
226
|
|
|
* |
227
|
|
|
* @param Collection $collection |
228
|
|
|
* @return Transformable |
229
|
|
|
* @throws InvalidArgumentException |
230
|
|
|
*/ |
231
|
|
|
protected function resolveModel( Collection $collection ):Transformable |
232
|
|
|
{ |
233
|
|
|
$class = $collection->first(); |
234
|
|
|
|
235
|
|
|
if ( ! $class instanceof Transformable ) { |
236
|
|
|
throw new InvalidArgumentException( 'Data must only contain models implementing the Transformable contract.' ); |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
$collection->each( function ( $model ) use ( $class ) { |
240
|
|
|
if ( get_class( $model ) !== get_class( $class ) ) { |
241
|
|
|
throw new InvalidArgumentException( 'You cannot transform arrays or collections with multiple model types.' ); |
242
|
|
|
} |
243
|
|
|
} ); |
244
|
|
|
|
245
|
|
|
return $class; |
246
|
|
|
} |
247
|
|
|
} |
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.