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\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 REST configuration. |
42
|
|
|
* |
43
|
|
|
* @var Rest\RestConfiguration |
44
|
|
|
*/ |
45
|
|
|
protected $config; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Constructor. |
49
|
|
|
* |
50
|
|
|
* @param SerializerInterface $serializer |
51
|
|
|
* @param NormalizerInterface $normalizer |
52
|
|
|
* @param StoreInterface $store |
53
|
|
|
* @param Rest\RestConfiguration $config |
54
|
|
|
*/ |
55
|
|
|
public function __construct(SerializerInterface $serializer, NormalizerInterface $normalizer, Store $store, Rest\RestConfiguration $config) |
56
|
|
|
{ |
57
|
|
|
$this->serializer = $serializer; |
58
|
|
|
$this->normalizer = $normalizer; |
59
|
|
|
$this->store = $store; |
60
|
|
|
$this->config = $config; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* {@inheritDoc} |
65
|
|
|
*/ |
66
|
|
|
abstract public function processRequest(Rest\RestRequest $request); |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* {@inheritDoc} |
70
|
|
|
*/ |
71
|
|
|
public function findRecord($typeKey, $identifier) //, array $fields = [], array $inclusions = []) |
|
|
|
|
72
|
|
|
{ |
73
|
|
|
$model = $this->getStore()->find($typeKey, $identifier); |
74
|
|
|
$payload = $this->serialize($model); |
75
|
|
|
return $this->createRestResponse(200, $payload); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* {@inheritDoc} |
80
|
|
|
*/ |
81
|
|
|
public function findAll($typeKey, array $identifiers = []) //, array $pagination = [], array $fields = [], array $inclusions = [], array $sort = []) |
|
|
|
|
82
|
|
|
{ |
83
|
|
|
$collection = $this->getStore()->findAll($typeKey, $identifiers); |
84
|
|
|
$payload = $this->serializeCollection($collection); |
85
|
|
|
return $this->createRestResponse(200, $payload); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* {@inheritDoc} |
90
|
|
|
*/ |
91
|
|
|
public function findQuery($typeKey, array $criteria, array $fields = [], array $sort = [], array $inclusions =[], $offset = 0, $limit = 0) |
92
|
|
|
{ |
93
|
|
|
$collection = $this->getStore()->findQuery($typeKey, $criteria, $fields, $sort, $offset, $limit); |
94
|
|
|
$payload = $this->serializeCollection($collection); |
95
|
|
|
return $this->createRestResponse(200, $payload); |
|
|
|
|
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* {@inheritDoc} |
100
|
|
|
*/ |
101
|
|
|
public function findRelationship($typeKey, $identifier, $fieldKey) |
102
|
|
|
{ |
103
|
|
|
$model = $this->getStore()->find($typeKey, $identifier); |
104
|
|
|
if (false === $model->isRelationship($fieldKey)) { |
105
|
|
|
throw AdapterException::badRequest(sprintf('The relationship field "%s" does not exist on model "%s"', $fieldKey, $typeKey)); |
106
|
|
|
} |
107
|
|
|
$rel = $model->get($fieldKey); |
108
|
|
|
$payload = (true === $model->isHasOne($fieldKey)) ? $this->serialize($rel) : $this->serializeArray($rel); |
109
|
|
|
return $this->createRestResponse(200, $payload); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* {@inheritDoc} |
114
|
|
|
*/ |
115
|
|
View Code Duplication |
public function createRecord($typeKey, Rest\RestPayload $payload) //, array $fields = [], array $inclusions = []) |
|
|
|
|
116
|
|
|
{ |
117
|
|
|
// @todo Do normalized payloads need to be wrapped in an object, similar to persistence Records? |
118
|
|
|
$normalized = $this->normalize($payload); |
119
|
|
|
$this->validateCreatePayload($typeKey, $normalized); |
120
|
|
|
|
121
|
|
|
$model = $this->getStore()->create($normalized['type']); |
122
|
|
|
$model->apply($normalized['properties']); |
123
|
|
|
$model->save(); |
124
|
|
|
$payload = $this->serialize($model); |
125
|
|
|
return $this->createRestResponse(201, $payload); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* {@inheritDoc} |
130
|
|
|
*/ |
131
|
|
View Code Duplication |
public function updateRecord($typeKey, $identifier, Rest\RestPayload $payload) // , array $fields = [], array $inclusions = []) |
|
|
|
|
132
|
|
|
{ |
133
|
|
|
$normalized = $this->normalize($payload); |
134
|
|
|
$this->validateUpdatePayload($typeKey, $identifier, $normalized); |
135
|
|
|
|
136
|
|
|
$model = $this->getStore()->find($typeKey, $normalized['id']); |
137
|
|
|
$model->apply($normalized['properties']); |
138
|
|
|
$model->save(); |
139
|
|
|
$payload = $this->serialize($model); |
140
|
|
|
return $this->createRestResponse(200, $payload); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* {@inheritDoc} |
145
|
|
|
*/ |
146
|
|
|
public function deleteRecord($typeKey, $identifier) |
147
|
|
|
{ |
148
|
|
|
$this->getStore()->delete($typeKey, $identifier); |
149
|
|
|
return $this->createRestResponse(204); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* {@inheritDoc} |
154
|
|
|
*/ |
155
|
|
|
public function autocomplete($typeKey, $attributeKey, $searchValue, array $pagination = []) |
156
|
|
|
{ |
157
|
|
|
$collection = $this->getStore()->searchAutocomplete($typeKey, $attributeKey, $searchValue); |
158
|
|
|
$payload = $this->serializeCollection($collection); |
159
|
|
|
return $this->createRestResponse(200, $payload); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* {@inheritDoc} |
164
|
|
|
*/ |
165
|
|
|
abstract public function buildUrl(EntityMetadata $metadata, $identifier, $relFieldKey = null, $isRelatedLink = false); |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* Validates a normalized create record payload. |
169
|
|
|
* |
170
|
|
|
* @param string $typeKey |
171
|
|
|
* @param array $normalized |
172
|
|
|
* @throws AdapterException On invalid create record payload. |
173
|
|
|
*/ |
174
|
|
|
abstract protected function validateCreatePayload($typeKey, array $normalized); |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Validates a normalized update record payload. |
178
|
|
|
* |
179
|
|
|
* @param string $typeKey |
180
|
|
|
* @param string $identifier |
181
|
|
|
* @param array $normalized |
182
|
|
|
* @throws AdapterException On invalid update record payload. |
183
|
|
|
*/ |
184
|
|
|
abstract protected function validateUpdatePayload($typeKey, $identifier, array $normalized); |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* Creates a RestResponse object based on common response parameters shared by this adapter. |
188
|
|
|
* |
189
|
|
|
* @param int $status |
190
|
|
|
* @param Rest\RestPayload $payload |
191
|
|
|
* @return Rest\RestResponse |
192
|
|
|
*/ |
193
|
|
|
abstract protected function createRestResponse($status, Rest\RestPayload $payload = null); |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* {@inheritDoc} |
197
|
|
|
*/ |
198
|
|
|
public function handleException(\Exception $e) |
199
|
|
|
{ |
200
|
|
|
$refl = new \ReflectionClass($e); |
201
|
|
|
if ($e instanceof HttpExceptionInterface) { |
202
|
|
|
$title = sprintf('%s::%s', $refl->getShortName(), $e->getErrorType()); |
203
|
|
|
$detail = $e->getMessage(); |
204
|
|
|
$status = $e->getHttpCode(); |
205
|
|
|
} else { |
206
|
|
|
$title = $refl->getShortName(); |
207
|
|
|
$detail = 'Oh no! Something bad happened on the server! Please try again.'; |
208
|
|
|
$status = 500; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
$serialized = $this->getSerializer()->serializeError($title, $detail, $status); |
212
|
|
|
$payload = new Rest\RestPayload($serialized); |
213
|
|
|
return $this->createRestResponse($status, $payload); |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* {@inheritDoc} |
218
|
|
|
*/ |
219
|
|
|
public function getStore() |
220
|
|
|
{ |
221
|
|
|
return $this->store; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* {@inheritDoc} |
226
|
|
|
*/ |
227
|
|
|
public function getSerializer() |
228
|
|
|
{ |
229
|
|
|
return $this->serializer; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* {@inheritDoc} |
234
|
|
|
*/ |
235
|
|
|
public function getNormalizer() |
236
|
|
|
{ |
237
|
|
|
return $this->normalizer; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* {@inheritDoc} |
242
|
|
|
*/ |
243
|
|
|
public function normalize(Rest\RestPayload $payload) |
244
|
|
|
{ |
245
|
|
|
return $this->getNormalizer()->normalize($payload, $this); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* {@inheritDoc} |
250
|
|
|
*/ |
251
|
|
|
public function serialize(Model $model = null) |
252
|
|
|
{ |
253
|
|
|
$serialized = $this->getSerializer()->serialize($model, $this); |
254
|
|
|
$this->validateSerialization($serialized); |
255
|
|
|
return new Rest\RestPayload($serialized); |
|
|
|
|
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* {@inheritDoc} |
260
|
|
|
*/ |
261
|
|
|
public function serializeArray(array $models) |
262
|
|
|
{ |
263
|
|
|
$serialized = $this->getSerializer()->serializeArray($models, $this); |
264
|
|
|
$this->validateSerialization($serialized); |
265
|
|
|
return new Rest\RestPayload($serialized); |
|
|
|
|
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* {@inheritDoc} |
270
|
|
|
*/ |
271
|
|
|
public function serializeCollection(Collection $collection) |
272
|
|
|
{ |
273
|
|
|
$serialized = $this->getSerializer()->serializeCollection($collection, $this); |
274
|
|
|
$this->validateSerialization($serialized); |
275
|
|
|
return new Rest\RestPayload($serialized); |
|
|
|
|
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* Validates that the serialized value is support by a RestPayload. |
280
|
|
|
* |
281
|
|
|
* @param mixed $serialized |
282
|
|
|
* @return bool |
283
|
|
|
* @throws AdapterException On invalid serialized value. |
284
|
|
|
*/ |
285
|
|
|
protected function validateSerialization($serialized) |
286
|
|
|
{ |
287
|
|
|
if (!is_string($serialized)) { |
288
|
|
|
throw AdapterException::badRequest('Unable to create an API response payload. Invalid serialization occurred.'); |
289
|
|
|
} |
290
|
|
|
return true; |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* {@inheritDoc} |
295
|
|
|
*/ |
296
|
|
|
public function getEntityMetadata($typeKey) |
297
|
|
|
{ |
298
|
|
|
return $this->getStore()->getMetadataForType($typeKey); |
299
|
|
|
} |
300
|
|
|
} |
301
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.