|
1
|
|
|
<?php namespace Pz\Doctrine\Rest\Response; |
|
2
|
|
|
|
|
3
|
|
|
use League\Fractal\Manager; |
|
4
|
|
|
use League\Fractal\Pagination\DoctrinePaginatorAdapter; |
|
5
|
|
|
use League\Fractal\Resource\Collection; |
|
6
|
|
|
use League\Fractal\Resource\Item; |
|
7
|
|
|
use League\Fractal\Serializer\JsonApiSerializer; |
|
8
|
|
|
use League\Fractal\TransformerAbstract; |
|
9
|
|
|
|
|
10
|
|
|
use Pz\Doctrine\Rest\RestException; |
|
11
|
|
|
use Pz\Doctrine\Rest\RestRequest; |
|
12
|
|
|
use Pz\Doctrine\Rest\RestResponse; |
|
13
|
|
|
use Pz\Doctrine\Rest\RestResponseFactory; |
|
14
|
|
|
use Pz\Doctrine\Rest\Contracts\JsonApiResource; |
|
15
|
|
|
|
|
16
|
|
|
use Doctrine\ORM\QueryBuilder; |
|
17
|
|
|
use Doctrine\ORM\Tools\Pagination\Paginator; |
|
18
|
|
|
|
|
19
|
|
|
use Symfony\Component\HttpFoundation\Response; |
|
20
|
|
|
|
|
21
|
|
|
class FractalResponseFactory implements RestResponseFactory |
|
22
|
|
|
{ |
|
23
|
|
|
const JSON_API_CONTENT_TYPE = 'application/vnd.api+json'; |
|
24
|
|
|
|
|
25
|
|
|
/** |
|
26
|
|
|
* @var TransformerAbstract |
|
27
|
|
|
*/ |
|
28
|
|
|
protected $transformer; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* @var string|null |
|
32
|
|
|
*/ |
|
33
|
|
|
protected $baseUrl; |
|
34
|
|
|
|
|
35
|
|
|
/** |
|
36
|
|
|
* FractalResponse constructor. |
|
37
|
|
|
* |
|
38
|
|
|
* @param string|null $baseUrl |
|
39
|
|
|
* @param TransformerAbstract|null $transformer |
|
40
|
|
|
*/ |
|
41
|
6 |
|
public function __construct($baseUrl = null, TransformerAbstract $transformer = null) |
|
42
|
|
|
{ |
|
43
|
6 |
|
$this->baseUrl = $baseUrl; |
|
44
|
6 |
|
$this->transformer($transformer); |
|
45
|
6 |
|
} |
|
46
|
|
|
|
|
47
|
|
|
/** |
|
48
|
|
|
* @param TransformerAbstract|null $transformer |
|
49
|
|
|
* |
|
50
|
|
|
* @return TransformerAbstract|$this |
|
51
|
|
|
*/ |
|
52
|
6 |
|
public function transformer(TransformerAbstract $transformer = null) |
|
53
|
|
|
{ |
|
54
|
6 |
|
if ($transformer !== null) { |
|
55
|
6 |
|
$this->transformer = $transformer; |
|
56
|
6 |
|
return $this; |
|
57
|
|
|
} |
|
58
|
|
|
|
|
59
|
6 |
|
return $this->transformer; |
|
60
|
|
|
} |
|
61
|
|
|
|
|
62
|
|
|
/** |
|
63
|
|
|
* @param RestRequest $request |
|
64
|
|
|
* @param QueryBuilder $qb |
|
65
|
|
|
* |
|
66
|
|
|
* @return RestResponse |
|
67
|
|
|
*/ |
|
68
|
6 |
|
public function index(RestRequest $request, QueryBuilder $qb) |
|
69
|
|
|
{ |
|
70
|
6 |
|
$headers = []; |
|
71
|
6 |
|
$resourceKey = $this->getIndexResourceKey($qb); |
|
72
|
6 |
|
$paginator = new Paginator($qb, false); |
|
73
|
6 |
|
$resource = new Collection($paginator, $this->transformer(), $resourceKey); |
|
74
|
|
|
|
|
75
|
6 |
|
if ($request->getLimit() !== null) { |
|
76
|
2 |
|
$resource->setPaginator(new DoctrinePaginatorAdapter($paginator, |
|
77
|
2 |
|
function(int $page) use ($resourceKey, $request) { |
|
78
|
1 |
|
return "{$this->baseUrl}/$resourceKey?".http_build_query([ |
|
79
|
|
|
'page' => [ |
|
80
|
1 |
|
'number' => $page, |
|
81
|
1 |
|
'size' => $request->getLimit() |
|
82
|
|
|
] |
|
83
|
|
|
]); |
|
84
|
2 |
|
} |
|
85
|
|
|
)); |
|
86
|
|
|
} |
|
87
|
|
|
|
|
88
|
6 |
|
if ($request->isAcceptJsonApi()) { |
|
89
|
6 |
|
$headers['Content-Type'] = static::JSON_API_CONTENT_TYPE; |
|
90
|
|
|
} |
|
91
|
|
|
|
|
92
|
6 |
|
return $this->response( |
|
93
|
6 |
|
$this->fractal($request) |
|
94
|
6 |
|
->parseFieldsets($request->getFields()) |
|
95
|
6 |
|
->createData($resource) |
|
96
|
6 |
|
->toArray(), |
|
97
|
6 |
|
RestResponse::HTTP_OK, |
|
98
|
6 |
|
$headers |
|
99
|
|
|
); |
|
100
|
|
|
} |
|
101
|
|
|
|
|
102
|
|
|
/** |
|
103
|
|
|
* @param RestRequest $request |
|
104
|
|
|
* @param object $entity |
|
105
|
|
|
* |
|
106
|
|
|
* @return RestResponse |
|
107
|
|
|
*/ |
|
108
|
|
View Code Duplication |
public function show(RestRequest $request, $entity) |
|
|
|
|
|
|
109
|
|
|
{ |
|
110
|
|
|
$headers = []; |
|
111
|
|
|
$resourceKey = null; |
|
112
|
|
|
|
|
113
|
|
|
if ($entity instanceof JsonApiResource) { |
|
114
|
|
|
$resourceKey = $entity->getResourceKey(); |
|
115
|
|
|
|
|
116
|
|
|
if ($request->isAcceptJsonApi()) { |
|
117
|
|
|
$headers['Location'] = $this->linkJsonApiResource($entity); |
|
118
|
|
|
$headers['Content-Type'] = static::JSON_API_CONTENT_TYPE; |
|
119
|
|
|
} |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
|
return $this->response( |
|
123
|
|
|
$this->fractal($request) |
|
124
|
|
|
->parseFieldsets($request->getFields()) |
|
125
|
|
|
->createData(new Item($entity, $this->transformer(), $resourceKey ?? null)) |
|
126
|
|
|
->toArray(), |
|
127
|
|
|
RestResponse::HTTP_OK, |
|
128
|
|
|
$headers |
|
129
|
|
|
); |
|
130
|
|
|
} |
|
131
|
|
|
|
|
132
|
|
|
/** |
|
133
|
|
|
* @param RestRequest $request |
|
134
|
|
|
* @param object $entity |
|
135
|
|
|
* |
|
136
|
|
|
* @return RestResponse |
|
137
|
|
|
*/ |
|
138
|
|
View Code Duplication |
public function create(RestRequest $request, $entity) |
|
|
|
|
|
|
139
|
|
|
{ |
|
140
|
|
|
$headers = []; |
|
141
|
|
|
$resourceKey = null; |
|
142
|
|
|
|
|
143
|
|
|
if ($entity instanceof JsonApiResource) { |
|
144
|
|
|
$resourceKey = $entity->getResourceKey(); |
|
145
|
|
|
|
|
146
|
|
|
if ($request->isAcceptJsonApi()) { |
|
147
|
|
|
$headers['Location'] = $this->linkJsonApiResource($entity); |
|
148
|
|
|
$headers['Content-Type'] = static::JSON_API_CONTENT_TYPE; |
|
149
|
|
|
} |
|
150
|
|
|
} |
|
151
|
|
|
|
|
152
|
|
|
return $this->response( |
|
153
|
|
|
$this->fractal($request) |
|
154
|
|
|
->parseFieldsets($request->getFields()) |
|
155
|
|
|
->createData(new Item($entity, $this->transformer(), $resourceKey)) |
|
156
|
|
|
->toArray(), |
|
157
|
|
|
Response::HTTP_CREATED, |
|
158
|
|
|
$headers |
|
159
|
|
|
); |
|
160
|
|
|
} |
|
161
|
|
|
|
|
162
|
|
|
/** |
|
163
|
|
|
* @param RestRequest $request |
|
164
|
|
|
* @param object $entity |
|
165
|
|
|
* |
|
166
|
|
|
* @return RestResponse |
|
167
|
|
|
*/ |
|
168
|
|
View Code Duplication |
public function update(RestRequest $request, $entity) |
|
|
|
|
|
|
169
|
|
|
{ |
|
170
|
|
|
$headers = []; |
|
171
|
|
|
$resourceKey = null; |
|
172
|
|
|
|
|
173
|
|
|
if ($entity instanceof JsonApiResource) { |
|
174
|
|
|
$resourceKey = $entity->getResourceKey(); |
|
175
|
|
|
|
|
176
|
|
|
if ($request->isAcceptJsonApi()) { |
|
177
|
|
|
$headers['Location'] = $this->linkJsonApiResource($entity); |
|
178
|
|
|
$headers['Content-Type'] = static::JSON_API_CONTENT_TYPE; |
|
179
|
|
|
} |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
return $this->response( |
|
183
|
|
|
$this->fractal($request) |
|
184
|
|
|
->parseFieldsets($request->getFields()) |
|
185
|
|
|
->createData(new Item($entity, $this->transformer(), $resourceKey)) |
|
186
|
|
|
->toArray(), |
|
187
|
|
|
Response::HTTP_OK, |
|
188
|
|
|
$headers |
|
189
|
|
|
); |
|
190
|
|
|
} |
|
191
|
|
|
|
|
192
|
|
|
/** |
|
193
|
|
|
* @param RestRequest $request |
|
194
|
|
|
* @param object $entity |
|
195
|
|
|
* |
|
196
|
|
|
* @return RestResponse |
|
197
|
|
|
*/ |
|
198
|
|
|
public function delete(RestRequest $request, $entity) |
|
199
|
|
|
{ |
|
200
|
|
|
return $this->response(null, RestResponse::HTTP_NO_CONTENT); |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
|
|
/** |
|
204
|
|
|
* @param RestRequest $request |
|
205
|
|
|
* |
|
206
|
|
|
* @return RestResponse |
|
207
|
|
|
*/ |
|
208
|
|
|
public function notFound(RestRequest $request) |
|
209
|
|
|
{ |
|
210
|
|
|
return $this->response(null, RestResponse::HTTP_NOT_FOUND); |
|
211
|
|
|
} |
|
212
|
|
|
|
|
213
|
|
|
/** |
|
214
|
|
|
* @param \Error|\Exception|\Pz\Doctrine\Rest\RestException $exception |
|
215
|
|
|
* |
|
216
|
|
|
* @return RestResponse |
|
217
|
|
|
* @throws \Error|\Exception|RestException |
|
218
|
|
|
*/ |
|
219
|
|
|
public function exception($exception) |
|
220
|
|
|
{ |
|
221
|
|
|
$message = $exception->getMessage(); |
|
222
|
|
|
|
|
223
|
|
|
switch (true) { |
|
224
|
|
|
case ($exception instanceof RestException): |
|
225
|
|
|
$httpStatus = $exception->httpStatus(); |
|
226
|
|
|
$errors = $exception->errors(); |
|
227
|
|
|
break; |
|
228
|
|
|
|
|
229
|
|
|
default: |
|
230
|
|
|
throw $exception; |
|
231
|
|
|
// $httpStatus = RestResponse::HTTP_INTERNAL_SERVER_ERROR; |
|
232
|
|
|
// $errors = $exception->getTrace(); |
|
233
|
|
|
break; |
|
234
|
|
|
} |
|
235
|
|
|
|
|
236
|
|
|
return $this->response(['message' => $message, 'errors' => $errors], $httpStatus); |
|
237
|
|
|
} |
|
238
|
|
|
|
|
239
|
|
|
/** |
|
240
|
|
|
* Return configured fractal by request format. |
|
241
|
|
|
* |
|
242
|
|
|
* @param RestRequest $request |
|
243
|
|
|
* |
|
244
|
|
|
* @return Manager |
|
245
|
|
|
*/ |
|
246
|
6 |
|
protected function fractal(RestRequest $request) |
|
247
|
|
|
{ |
|
248
|
6 |
|
$fractal = new Manager(); |
|
249
|
|
|
|
|
250
|
6 |
|
if ($request->isAcceptJsonApi()) { |
|
251
|
6 |
|
$fractal->setSerializer(new JsonApiSerializer($this->baseUrl)); |
|
252
|
|
|
} |
|
253
|
|
|
|
|
254
|
6 |
|
if ($includes = $request->getInclude()) { |
|
255
|
2 |
|
$fractal->parseIncludes($includes); |
|
256
|
|
|
} |
|
257
|
|
|
|
|
258
|
6 |
|
if ($excludes = $request->getExclude()) { |
|
259
|
|
|
$fractal->parseExcludes($excludes); |
|
260
|
|
|
} |
|
261
|
|
|
|
|
262
|
6 |
|
return $fractal; |
|
263
|
|
|
} |
|
264
|
|
|
|
|
265
|
|
|
/** |
|
266
|
|
|
* @param mixed $data |
|
267
|
|
|
* @param int $httStatus |
|
268
|
|
|
* @param array $headers |
|
269
|
|
|
* |
|
270
|
|
|
* @return RestResponse |
|
271
|
|
|
*/ |
|
272
|
6 |
|
protected function response($data = null, $httStatus = RestResponse::HTTP_OK, array $headers = []) |
|
273
|
|
|
{ |
|
274
|
6 |
|
return new RestResponse($data, $httStatus, $headers); |
|
275
|
|
|
} |
|
276
|
|
|
|
|
277
|
|
|
/** |
|
278
|
|
|
* @param QueryBuilder $qb |
|
279
|
|
|
* |
|
280
|
|
|
* @return string |
|
281
|
|
|
*/ |
|
282
|
6 |
|
protected function getIndexResourceKey(QueryBuilder $qb) |
|
283
|
|
|
{ |
|
284
|
6 |
|
$class = $qb->getRootEntities()[0]; |
|
285
|
6 |
|
if (isset(class_implements($class)[JsonApiResource::class])) { |
|
286
|
6 |
|
return call_user_func("$class::getResourceKey"); |
|
287
|
|
|
} |
|
288
|
|
|
|
|
289
|
|
|
return $qb->getRootAliases()[0]; |
|
290
|
|
|
} |
|
291
|
|
|
|
|
292
|
|
|
/** |
|
293
|
|
|
* @param JsonApiResource $resource |
|
294
|
|
|
* |
|
295
|
|
|
* @return string|null |
|
296
|
|
|
*/ |
|
297
|
|
|
protected function linkJsonApiResource(JsonApiResource $resource) |
|
298
|
|
|
{ |
|
299
|
|
|
return sprintf('%s/%s/%s', $this->baseUrl, $resource->getResourceKey(), $resource->getId()); |
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
/** |
|
303
|
|
|
* @param RestRequest $request |
|
304
|
|
|
* |
|
305
|
|
|
* @return \Closure |
|
306
|
|
|
*/ |
|
307
|
|
|
protected function getPaginatorRouteGenerator(RestRequest $request) |
|
|
|
|
|
|
308
|
|
|
{ |
|
309
|
|
|
return function(int $page) { |
|
|
|
|
|
|
310
|
|
|
return null; |
|
311
|
|
|
}; |
|
312
|
|
|
} |
|
313
|
|
|
} |
|
314
|
|
|
|
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.