1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace As3\Modlr\Api; |
4
|
|
|
|
5
|
|
|
use As3\Modlr\Exception\HttpExceptionInterface; |
6
|
|
|
use As3\Modlr\Metadata\EntityMetadata; |
7
|
|
|
use As3\Modlr\Models\Collections\Collection; |
8
|
|
|
use As3\Modlr\Models\Model; |
9
|
|
|
use As3\Modlr\Rest; |
10
|
|
|
use As3\Modlr\Store\Store; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Abstract Adapter implementation for handling API operations. |
14
|
|
|
* |
15
|
|
|
* @author Jacob Bare <[email protected]> |
16
|
|
|
*/ |
17
|
|
|
abstract class AbstractAdapter implements AdapterInterface |
18
|
|
|
{ |
19
|
|
|
/** |
20
|
|
|
* The Serializer |
21
|
|
|
* |
22
|
|
|
* @var SerializerInterface |
23
|
|
|
*/ |
24
|
|
|
protected $serializer; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* The Normalizer |
28
|
|
|
* |
29
|
|
|
* @var NormalizerInterface |
30
|
|
|
*/ |
31
|
|
|
protected $normalizer; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* The Store to use for persistence operations. |
35
|
|
|
* |
36
|
|
|
* @var Store |
37
|
|
|
*/ |
38
|
|
|
protected $store; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* The current REST request. |
42
|
|
|
* |
43
|
|
|
* @var Rest\RestRequest |
44
|
|
|
*/ |
45
|
|
|
protected $request; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* The REST configuration. |
49
|
|
|
* |
50
|
|
|
* @var Rest\RestConfiguration |
51
|
|
|
*/ |
52
|
|
|
protected $config; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Constructor. |
56
|
|
|
* |
57
|
|
|
* @param SerializerInterface $serializer |
58
|
|
|
* @param NormalizerInterface $normalizer |
59
|
|
|
* @param Store $store |
60
|
|
|
* @param Rest\RestConfiguration $config |
61
|
|
|
*/ |
62
|
|
|
public function __construct(SerializerInterface $serializer, NormalizerInterface $normalizer, Store $store, Rest\RestConfiguration $config) |
63
|
|
|
{ |
64
|
|
|
$this->serializer = $serializer; |
65
|
|
|
$this->normalizer = $normalizer; |
66
|
|
|
$this->store = $store; |
67
|
|
|
$this->config = $config; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* {@inheritDoc} |
72
|
|
|
*/ |
73
|
|
|
abstract public function processRequest(Rest\RestRequest $request); |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* {@inheritDoc} |
77
|
|
|
*/ |
78
|
|
|
public function findRecord($typeKey, $identifier, array $fields = [], array $inclusions = []) |
79
|
|
|
{ |
80
|
|
|
$model = $this->getStore()->find($typeKey, $identifier); |
81
|
|
|
$payload = $this->serialize($model); |
82
|
|
|
return $this->createRestResponse(200, $payload); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* {@inheritDoc} |
87
|
|
|
*/ |
88
|
|
|
public function findAll($typeKey, array $identifiers = [], array $fields = [], array $sort = [], array $pagination = [], array $inclusions = []) |
89
|
|
|
{ |
90
|
|
|
$collection = $this->getStore()->findAll($typeKey, $identifiers, $fields, $sort, $pagination['offset'], $pagination['limit']); |
91
|
|
|
$payload = $this->serializeCollection($collection); |
92
|
|
|
return $this->createRestResponse(200, $payload); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* {@inheritDoc} |
97
|
|
|
*/ |
98
|
|
|
public function findQuery($typeKey, array $criteria, array $fields = [], array $sort = [], array $pagination = [], array $inclusions = []) |
99
|
|
|
{ |
100
|
|
|
$collection = $this->getStore()->findQuery($typeKey, $criteria, $fields, $sort, $pagination['offset'], $pagination['limit']); |
101
|
|
|
$payload = $this->serializeCollection($collection); |
102
|
|
|
return $this->createRestResponse(200, $payload); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* {@inheritDoc} |
107
|
|
|
*/ |
108
|
|
|
public function findRelationship($typeKey, $identifier, $fieldKey) |
109
|
|
|
{ |
110
|
|
|
$model = $this->getStore()->find($typeKey, $identifier); |
111
|
|
|
if (false === $model->isRelationship($fieldKey)) { |
112
|
|
|
throw AdapterException::badRequest(sprintf('The relationship field "%s" does not exist on model "%s"', $fieldKey, $typeKey)); |
113
|
|
|
} |
114
|
|
|
$rel = $model->get($fieldKey); |
115
|
|
|
$payload = (true === $model->isHasOne($fieldKey)) ? $this->serialize($rel) : $this->serializeArray($rel); |
116
|
|
|
return $this->createRestResponse(200, $payload); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* {@inheritDoc} |
121
|
|
|
*/ |
122
|
|
View Code Duplication |
public function createRecord($typeKey, Rest\RestPayload $payload) //, array $fields = [], array $inclusions = []) |
|
|
|
|
123
|
|
|
{ |
124
|
|
|
$normalized = $this->normalize($payload); |
125
|
|
|
$this->validateCreatePayload($typeKey, $normalized); |
126
|
|
|
|
127
|
|
|
$model = $this->getStore()->create($normalized['type']); |
128
|
|
|
$model->apply($normalized['properties']); |
129
|
|
|
$model->save(); |
130
|
|
|
$payload = $this->serialize($model); |
131
|
|
|
return $this->createRestResponse(201, $payload); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* {@inheritDoc} |
136
|
|
|
*/ |
137
|
|
View Code Duplication |
public function updateRecord($typeKey, $identifier, Rest\RestPayload $payload) // , array $fields = [], array $inclusions = []) |
|
|
|
|
138
|
|
|
{ |
139
|
|
|
$normalized = $this->normalize($payload); |
140
|
|
|
$this->validateUpdatePayload($typeKey, $identifier, $normalized); |
141
|
|
|
|
142
|
|
|
$model = $this->getStore()->find($typeKey, $normalized['id']); |
143
|
|
|
$model->apply($normalized['properties']); |
144
|
|
|
$model->save(); |
145
|
|
|
$payload = $this->serialize($model); |
146
|
|
|
return $this->createRestResponse(200, $payload); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* {@inheritDoc} |
151
|
|
|
*/ |
152
|
|
|
public function deleteRecord($typeKey, $identifier) |
153
|
|
|
{ |
154
|
|
|
$this->getStore()->delete($typeKey, $identifier); |
155
|
|
|
return $this->createRestResponse(204); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* {@inheritDoc} |
160
|
|
|
*/ |
161
|
|
|
public function autocomplete($typeKey, $attributeKey, $searchValue, array $pagination = []) |
162
|
|
|
{ |
163
|
|
|
$collection = $this->getStore()->searchAutocomplete($typeKey, $attributeKey, $searchValue); |
164
|
|
|
$payload = $this->serializeCollection($collection); |
165
|
|
|
return $this->createRestResponse(200, $payload); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* {@inheritDoc} |
170
|
|
|
*/ |
171
|
|
|
public function getRequest() |
172
|
|
|
{ |
173
|
|
|
return $this->request; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* {@inheritDoc} |
178
|
|
|
*/ |
179
|
|
|
abstract public function buildUrl(EntityMetadata $metadata, $identifier, $relFieldKey = null, $isRelatedLink = false); |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Validates a normalized create record payload. |
183
|
|
|
* |
184
|
|
|
* @param string $typeKey |
185
|
|
|
* @param array $normalized |
186
|
|
|
* @throws AdapterException On invalid create record payload. |
187
|
|
|
*/ |
188
|
|
|
abstract protected function validateCreatePayload($typeKey, array $normalized); |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Validates a normalized update record payload. |
192
|
|
|
* |
193
|
|
|
* @param string $typeKey |
194
|
|
|
* @param string $identifier |
195
|
|
|
* @param array $normalized |
196
|
|
|
* @throws AdapterException On invalid update record payload. |
197
|
|
|
*/ |
198
|
|
|
abstract protected function validateUpdatePayload($typeKey, $identifier, array $normalized); |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Creates a RestResponse object based on common response parameters shared by this adapter. |
202
|
|
|
* |
203
|
|
|
* @param int $status |
204
|
|
|
* @param Rest\RestPayload $payload |
205
|
|
|
* @return Rest\RestResponse |
206
|
|
|
*/ |
207
|
|
|
abstract protected function createRestResponse($status, Rest\RestPayload $payload = null); |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* {@inheritDoc} |
211
|
|
|
*/ |
212
|
|
|
public function handleException(\Exception $e) |
213
|
|
|
{ |
214
|
|
|
$refl = new \ReflectionClass($e); |
215
|
|
|
if ($e instanceof HttpExceptionInterface) { |
216
|
|
|
$title = sprintf('%s::%s', $refl->getShortName(), $e->getErrorType()); |
217
|
|
|
$detail = $e->getMessage(); |
218
|
|
|
$status = $e->getHttpCode(); |
219
|
|
|
} else { |
220
|
|
|
$title = $refl->getShortName(); |
221
|
|
|
$detail = 'Oh no! Something bad happened on the server! Please try again.'; |
222
|
|
|
$status = 500; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
$serialized = $this->getSerializer()->serializeError($title, $detail, $status); |
226
|
|
|
$payload = new Rest\RestPayload($serialized); |
227
|
|
|
return $this->createRestResponse($status, $payload); |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* {@inheritDoc} |
232
|
|
|
*/ |
233
|
|
|
public function getStore() |
234
|
|
|
{ |
235
|
|
|
return $this->store; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* {@inheritDoc} |
240
|
|
|
*/ |
241
|
|
|
public function getSerializer() |
242
|
|
|
{ |
243
|
|
|
return $this->serializer; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* {@inheritDoc} |
248
|
|
|
*/ |
249
|
|
|
public function getNormalizer() |
250
|
|
|
{ |
251
|
|
|
return $this->normalizer; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* {@inheritDoc} |
256
|
|
|
*/ |
257
|
|
|
public function normalize(Rest\RestPayload $payload) |
258
|
|
|
{ |
259
|
|
|
return $this->getNormalizer()->normalize($payload, $this); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* {@inheritDoc} |
264
|
|
|
*/ |
265
|
|
|
public function serialize(Model $model = null) |
266
|
|
|
{ |
267
|
|
|
$serialized = (String) $this->getSerializer()->serialize($model, $this); |
268
|
|
|
$this->validateSerialization($serialized); |
269
|
|
|
return new Rest\RestPayload($serialized); |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* {@inheritDoc} |
274
|
|
|
*/ |
275
|
|
|
public function serializeArray(array $models) |
276
|
|
|
{ |
277
|
|
|
$serialized = (String) $this->getSerializer()->serializeArray($models, $this); |
278
|
|
|
$this->validateSerialization($serialized); |
279
|
|
|
return new Rest\RestPayload($serialized); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
/** |
283
|
|
|
* {@inheritDoc} |
284
|
|
|
*/ |
285
|
|
|
public function serializeCollection(Collection $collection) |
286
|
|
|
{ |
287
|
|
|
$serialized = (String) $this->getSerializer()->serializeCollection($collection, $this); |
288
|
|
|
$this->validateSerialization($serialized); |
289
|
|
|
return new Rest\RestPayload($serialized); |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Validates that the serialized value is support by a RestPayload. |
294
|
|
|
* |
295
|
|
|
* @param mixed $serialized |
296
|
|
|
* @return bool |
297
|
|
|
* @throws AdapterException On invalid serialized value. |
298
|
|
|
*/ |
299
|
|
|
protected function validateSerialization($serialized) |
300
|
|
|
{ |
301
|
|
|
if (!is_string($serialized)) { |
302
|
|
|
throw AdapterException::badRequest('Unable to create an API response payload. Invalid serialization occurred.'); |
303
|
|
|
} |
304
|
|
|
return true; |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
/** |
308
|
|
|
* {@inheritDoc} |
309
|
|
|
*/ |
310
|
|
|
public function getEntityMetadata($typeKey) |
311
|
|
|
{ |
312
|
|
|
return $this->getStore()->getMetadataForType($typeKey); |
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
|
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.