1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* basic rest controller |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
namespace Graviton\RestBundle\Controller; |
7
|
|
|
|
8
|
|
|
use Graviton\DocumentBundle\Service\FormDataMapperInterface; |
9
|
|
|
use Graviton\ExceptionBundle\Exception\DeserializationException; |
10
|
|
|
use Graviton\ExceptionBundle\Exception\InvalidJsonPatchException; |
11
|
|
|
use Graviton\ExceptionBundle\Exception\MalformedInputException; |
12
|
|
|
use Graviton\ExceptionBundle\Exception\NotFoundException; |
13
|
|
|
use Graviton\ExceptionBundle\Exception\SerializationException; |
14
|
|
|
use Graviton\RestBundle\Validator\Form; |
15
|
|
|
use Graviton\RestBundle\Model\DocumentModel; |
16
|
|
|
use Graviton\RestBundle\Model\PaginatorAwareInterface; |
17
|
|
|
use Graviton\SchemaBundle\SchemaUtils; |
18
|
|
|
use Graviton\DocumentBundle\Form\Type\DocumentType; |
19
|
|
|
use Graviton\RestBundle\Service\RestUtilsInterface; |
20
|
|
|
use Graviton\SecurityBundle\Entities\SecurityUser; |
21
|
|
|
use Knp\Component\Pager\Paginator; |
22
|
|
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
23
|
|
|
use Symfony\Component\HttpFoundation\Request; |
24
|
|
|
use Symfony\Component\HttpFoundation\Response; |
25
|
|
|
use Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException; |
26
|
|
|
use Symfony\Component\Routing\Exception\RouteNotFoundException; |
27
|
|
|
use Symfony\Component\Form\FormFactory; |
28
|
|
|
use Symfony\Bundle\FrameworkBundle\Routing\Router; |
29
|
|
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; |
30
|
|
|
use Symfony\Component\Validator\Validator\ValidatorInterface; |
31
|
|
|
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface; |
32
|
|
|
use Rs\Json\Patch; |
33
|
|
|
use Rs\Json\Patch\InvalidPatchDocumentJsonException; |
34
|
|
|
use Rs\Json\Patch\InvalidTargetDocumentJsonException; |
35
|
|
|
use Rs\Json\Patch\InvalidOperationException; |
36
|
|
|
use Rs\Json\Patch\FailedTestException; |
37
|
|
|
use Graviton\RestBundle\Service\JsonPatchValidator; |
38
|
|
|
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* This is a basic rest controller. It should fit the most needs but if you need to add some |
42
|
|
|
* extra functionality you can extend it and overwrite single/all actions. |
43
|
|
|
* You can also extend the model class to add some extra logic before save |
44
|
|
|
* |
45
|
|
|
* @author List of contributors <https://github.com/libgraviton/graviton/graphs/contributors> |
46
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GNU Public License |
47
|
|
|
* @link http://swisscom.ch |
48
|
|
|
*/ |
49
|
|
|
class RestController |
50
|
|
|
{ |
51
|
|
|
/** |
52
|
|
|
* @var DocumentModel |
53
|
|
|
*/ |
54
|
|
|
private $model; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* @var ContainerInterface service_container |
58
|
|
|
*/ |
59
|
|
|
private $container; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* @var Response |
63
|
|
|
*/ |
64
|
|
|
private $response; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* @var FormFactory |
68
|
|
|
*/ |
69
|
|
|
private $formFactory; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* @var DocumentType |
73
|
|
|
*/ |
74
|
|
|
private $formType; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @var RestUtilsInterface |
78
|
|
|
*/ |
79
|
|
|
private $restUtils; |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @var SchemaUtils |
83
|
|
|
*/ |
84
|
|
|
private $schemaUtils; |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* @var FormDataMapperInterface |
88
|
|
|
*/ |
89
|
|
|
protected $formDataMapper; |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* @var Router |
93
|
|
|
*/ |
94
|
|
|
private $router; |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* @var ValidatorInterface |
98
|
|
|
*/ |
99
|
|
|
private $validator; |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* @var EngineInterface |
103
|
|
|
*/ |
104
|
|
|
private $templating; |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* @var JsonPatchValidator |
108
|
|
|
*/ |
109
|
|
|
private $jsonPatchValidator; |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* @var Form |
113
|
|
|
*/ |
114
|
|
|
protected $formValidator; |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* @var TokenStorage |
118
|
|
|
*/ |
119
|
|
|
protected $tokenStorage; |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* @param Response $response Response |
123
|
|
|
* @param RestUtilsInterface $restUtils Rest utils |
124
|
|
|
* @param Router $router Router |
125
|
|
|
* @param ValidatorInterface $validator Validator |
126
|
|
|
* @param EngineInterface $templating Templating |
127
|
|
|
* @param FormFactory $formFactory form factory |
128
|
|
|
* @param DocumentType $formType generic form |
129
|
2 |
|
* @param ContainerInterface $container Container |
130
|
|
|
* @param SchemaUtils $schemaUtils Schema utils |
131
|
|
|
*/ |
132
|
|
|
public function __construct( |
133
|
|
|
Response $response, |
134
|
|
|
RestUtilsInterface $restUtils, |
135
|
|
|
Router $router, |
136
|
|
|
ValidatorInterface $validator, |
137
|
|
|
EngineInterface $templating, |
138
|
|
|
FormFactory $formFactory, |
139
|
|
|
DocumentType $formType, |
140
|
2 |
|
ContainerInterface $container, |
141
|
2 |
|
SchemaUtils $schemaUtils |
142
|
2 |
|
) { |
143
|
2 |
|
$this->response = $response; |
144
|
2 |
|
$this->restUtils = $restUtils; |
145
|
2 |
|
$this->router = $router; |
146
|
2 |
|
$this->validator = $validator; |
147
|
2 |
|
$this->templating = $templating; |
148
|
2 |
|
$this->formFactory = $formFactory; |
149
|
2 |
|
$this->formType = $formType; |
150
|
|
|
$this->container = $container; |
151
|
|
|
$this->schemaUtils = $schemaUtils; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Setter for the tokenStorage |
156
|
2 |
|
* |
157
|
|
|
* @param TokenStorage $tokenStorage The token storage |
158
|
2 |
|
* @return void |
159
|
2 |
|
*/ |
160
|
|
|
public function setTokenStorage(TokenStorage $tokenStorage) |
161
|
|
|
{ |
162
|
|
|
$this->tokenStorage = $tokenStorage; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Set form data mapper |
167
|
2 |
|
* |
168
|
|
|
* @param FormDataMapperInterface $formDataMapper Form data mapper |
169
|
2 |
|
* @return void |
170
|
2 |
|
*/ |
171
|
|
|
public function setFormDataMapper(FormDataMapperInterface $formDataMapper) |
172
|
|
|
{ |
173
|
|
|
$this->formDataMapper = $formDataMapper; |
174
|
|
|
} |
175
|
|
|
|
176
|
2 |
|
/** |
177
|
|
|
* @param JsonPatchValidator $jsonPatchValidator Service for validation json patch |
178
|
2 |
|
* @return void |
179
|
2 |
|
*/ |
180
|
|
|
public function setJsonPatchValidator(JsonPatchValidator $jsonPatchValidator) |
181
|
|
|
{ |
182
|
|
|
$this->jsonPatchValidator = $jsonPatchValidator; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Defines the Form validator to be used. |
187
|
|
|
* |
188
|
2 |
|
* @param Form $validator Validator to be used |
189
|
|
|
* |
190
|
2 |
|
* @return void |
191
|
2 |
|
*/ |
192
|
|
|
public function setFormValidator(Form $validator) |
193
|
|
|
{ |
194
|
|
|
$this->formValidator = $validator; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* Get the container object |
199
|
|
|
* |
200
|
|
|
* @return \Symfony\Component\DependencyInjection\ContainerInterface |
201
|
|
|
* |
202
|
|
|
* @obsolete |
203
|
|
|
*/ |
204
|
|
|
public function getContainer() |
205
|
|
|
{ |
206
|
|
|
return $this->container; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** |
210
|
|
|
* Returns a single record |
211
|
|
|
* |
212
|
|
|
* @param Request $request Current http request |
213
|
1 |
|
* @param string $id ID of record |
214
|
|
|
* |
215
|
1 |
|
* @return \Symfony\Component\HttpFoundation\Response $response Response with result or error |
216
|
1 |
|
*/ |
217
|
|
|
public function getAction(Request $request, $id) |
218
|
1 |
|
{ |
219
|
|
|
$response = $this->getResponse() |
220
|
1 |
|
->setStatusCode(Response::HTTP_OK); |
221
|
1 |
|
|
222
|
1 |
|
$record = $this->findRecord($id); |
223
|
|
|
|
224
|
1 |
|
return $this->render( |
225
|
|
|
'GravitonRestBundle:Main:index.json.twig', |
226
|
|
|
['response' => $this->serialize($record)], |
227
|
|
|
$response |
228
|
|
|
); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
2 |
|
* Get the response object |
233
|
|
|
* |
234
|
2 |
|
* @return \Symfony\Component\HttpFoundation\Response $response Response object |
235
|
|
|
*/ |
236
|
|
|
public function getResponse() |
237
|
|
|
{ |
238
|
|
|
return $this->response; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* Get a single record from database or throw an exception if it doesn't exist |
243
|
|
|
* |
244
|
|
|
* @param mixed $id Record id |
245
|
|
|
* |
246
|
1 |
|
* @throws \Graviton\ExceptionBundle\Exception\NotFoundException |
247
|
|
|
* |
248
|
1 |
|
* @return object $record Document object |
249
|
|
|
*/ |
250
|
1 |
|
protected function findRecord($id) |
251
|
|
|
{ |
252
|
|
|
$response = $this->getResponse(); |
253
|
|
|
|
254
|
|
|
if (!($record = $this->getModel()->find($id))) { |
255
|
|
|
$e = new NotFoundException("Entry with id " . $id . " not found!"); |
256
|
1 |
|
$e->setResponse($response); |
257
|
|
|
throw $e; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
return $record; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* Return the model |
265
|
|
|
* |
266
|
2 |
|
* @throws \Exception in case no model was defined. |
267
|
|
|
* |
268
|
2 |
|
* @return DocumentModel $model Model |
269
|
|
|
*/ |
270
|
|
|
public function getModel() |
271
|
|
|
{ |
272
|
2 |
|
if (!$this->model) { |
273
|
|
|
throw new \Exception('No model is set for this controller'); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
return $this->model; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* Set the model class |
281
|
|
|
* |
282
|
2 |
|
* @param DocumentModel $model Model class |
283
|
|
|
* |
284
|
2 |
|
* @return self |
285
|
|
|
*/ |
286
|
2 |
|
public function setModel(DocumentModel $model) |
287
|
|
|
{ |
288
|
|
|
$this->model = $model; |
289
|
|
|
|
290
|
|
|
return $this; |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Serialize the given record and throw an exception if something went wrong |
295
|
|
|
* |
296
|
|
|
* @param object|object[] $result Record(s) |
297
|
|
|
* |
298
|
2 |
|
* @throws \Graviton\ExceptionBundle\Exception\SerializationException |
299
|
|
|
* |
300
|
2 |
|
* @return string $content Json content |
301
|
|
|
*/ |
302
|
|
|
protected function serialize($result) |
303
|
|
|
{ |
304
|
|
|
$response = $this->getResponse(); |
305
|
2 |
|
|
306
|
1 |
|
try { |
307
|
1 |
|
// array is serialized as an object {"0":{...},"1":{...},...} when data contains an empty objects |
308
|
1 |
|
// we serialize each item because we can assume this bug affects only root array element |
309
|
1 |
|
if (is_array($result) && array_keys($result) === range(0, count($result) - 1)) { |
310
|
|
|
$result = array_map( |
311
|
1 |
|
function ($item) { |
312
|
1 |
|
return $this->getRestUtils()->serializeContent($item); |
313
|
|
|
}, |
314
|
|
|
$result |
315
|
1 |
|
); |
316
|
|
|
return '['.implode(',', $result).']'; |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
return $this->getRestUtils()->serializeContent($result); |
|
|
|
|
320
|
|
|
} catch (\Exception $e) { |
321
|
|
|
$exception = new SerializationException($e); |
322
|
|
|
$exception->setResponse($response); |
323
|
|
|
throw $exception; |
324
|
|
|
} |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** |
328
|
2 |
|
* Get RestUtils service |
329
|
|
|
* |
330
|
2 |
|
* @return \Graviton\RestBundle\Service\RestUtils |
331
|
|
|
*/ |
332
|
|
|
public function getRestUtils() |
333
|
|
|
{ |
334
|
|
|
return $this->restUtils; |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
/** |
338
|
|
|
* Returns all records |
339
|
|
|
* |
340
|
1 |
|
* @param Request $request Current http request |
341
|
|
|
* |
342
|
1 |
|
* @return \Symfony\Component\HttpFoundation\Response $response Response with result or error |
343
|
1 |
|
*/ |
344
|
|
|
public function allAction(Request $request) |
345
|
1 |
|
{ |
346
|
|
|
$model = $this->getModel(); |
347
|
|
|
|
348
|
|
|
// Security is optional configured in Parameters |
349
|
|
|
try { |
350
|
1 |
|
/** @var SecurityUser $securityUser */ |
351
|
1 |
|
$securityUser = $this->getSecurityUser(); |
352
|
|
|
} catch (PreconditionRequiredHttpException $e) { |
353
|
1 |
|
$securityUser = null; |
354
|
1 |
|
} |
355
|
1 |
|
|
356
|
|
|
if ($model instanceof PaginatorAwareInterface && !$model->hasPaginator()) { |
357
|
1 |
|
$paginator = new Paginator(); |
358
|
|
|
$model->setPaginator($paginator); |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
$response = $this->getResponse() |
362
|
|
|
->setStatusCode(Response::HTTP_OK); |
363
|
|
|
|
364
|
|
|
return $this->render( |
365
|
|
|
'GravitonRestBundle:Main:index.json.twig', |
366
|
|
|
['response' => $this->serialize($model->findAll($request, $securityUser))], |
367
|
|
|
$response |
368
|
|
|
); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* Writes a new Entry to the database |
373
|
|
|
* |
374
|
|
|
* @param Request $request Current http request |
375
|
|
|
* |
376
|
|
|
* @return \Symfony\Component\HttpFoundation\Response $response Result of action with data (if successful) |
377
|
|
|
*/ |
378
|
|
|
public function postAction(Request $request) |
379
|
|
|
{ |
380
|
|
|
// Get the response object from container |
381
|
|
|
$response = $this->getResponse(); |
382
|
|
|
$model = $this->getModel(); |
383
|
|
|
|
384
|
|
|
$this->formValidator->checkJsonRequest($request, $response); |
385
|
|
|
$record = $this->formValidator->checkForm( |
386
|
|
|
$this->formValidator->getForm($request, $model), |
387
|
|
|
$model, |
388
|
|
|
$this->formDataMapper, |
389
|
|
|
$request->getContent() |
|
|
|
|
390
|
|
|
); |
391
|
|
|
|
392
|
|
|
// Insert the new record |
393
|
|
|
$record = $this->getModel()->insertRecord($record); |
394
|
|
|
|
395
|
|
|
// store id of new record so we dont need to reparse body later when needed |
396
|
|
|
$request->attributes->set('id', $record->getId()); |
397
|
|
|
|
398
|
|
|
// Set status code |
399
|
|
|
$response->setStatusCode(Response::HTTP_CREATED); |
400
|
|
|
|
401
|
|
|
$response->headers->set( |
402
|
|
|
'Location', |
403
|
|
|
$this->getRouter()->generate($this->getRouteName($request), array('id' => $record->getId())) |
404
|
|
|
); |
405
|
|
|
|
406
|
|
|
return $response; |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
/** |
410
|
|
|
* Deserialize the given content throw an exception if something went wrong |
411
|
|
|
* |
412
|
|
|
* @param string $content Request content |
413
|
|
|
* @param string $documentClass Document class |
414
|
|
|
* |
415
|
|
|
* @throws DeserializationException |
416
|
|
|
* |
417
|
|
|
* @return object $record Document |
418
|
|
|
*/ |
419
|
|
|
protected function deserialize($content, $documentClass) |
420
|
|
|
{ |
421
|
|
|
$response = $this->getResponse(); |
422
|
|
|
|
423
|
|
|
try { |
424
|
|
|
$record = $this->getRestUtils()->deserializeContent( |
425
|
|
|
$content, |
426
|
|
|
$documentClass |
427
|
|
|
); |
428
|
|
|
} catch (\Exception $e) { |
429
|
|
|
// pass the previous exception in this case to get the error message in the handler |
430
|
|
|
// http://php.net/manual/de/exception.getprevious.php |
431
|
|
|
$exception = new DeserializationException("Deserialization failed", $e); |
432
|
|
|
|
433
|
|
|
// at the moment, the response has to be set on the exception object. |
434
|
|
|
// try to refactor this and return the graviton.rest.response if none is set... |
435
|
|
|
$exception->setResponse($response); |
436
|
1 |
|
throw $exception; |
437
|
|
|
} |
438
|
1 |
|
|
439
|
|
|
return $record; |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
/** |
443
|
|
|
* Get the router from the dic |
444
|
|
|
* |
445
|
|
|
* @return Router |
446
|
|
|
*/ |
447
|
|
|
public function getRouter() |
448
|
|
|
{ |
449
|
|
|
return $this->router; |
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
/** |
453
|
|
|
* Update a record |
454
|
|
|
* |
455
|
|
|
* @param Number $id ID of record |
456
|
|
|
* @param Request $request Current http request |
457
|
|
|
* |
458
|
|
|
* @throws MalformedInputException |
459
|
|
|
* |
460
|
|
|
* @return Response $response Result of action with data (if successful) |
461
|
|
|
*/ |
462
|
|
|
public function putAction($id, Request $request) |
463
|
|
|
{ |
464
|
|
|
$response = $this->getResponse(); |
465
|
|
|
$model = $this->getModel(); |
466
|
|
|
|
467
|
|
|
$this->formValidator->checkJsonRequest($request, $response); |
468
|
|
|
|
469
|
|
|
$record = $this->formValidator->checkForm( |
470
|
|
|
$this->formValidator->getForm($request, $model), |
471
|
|
|
$model, |
472
|
|
|
$this->formDataMapper, |
473
|
|
|
$request->getContent() |
|
|
|
|
474
|
|
|
); |
475
|
|
|
|
476
|
|
|
// does it really exist?? |
477
|
|
|
$upsert = false; |
478
|
|
|
try { |
479
|
|
|
$this->findRecord($id); |
480
|
|
|
} catch (NotFoundException $e) { |
481
|
|
|
// who cares, we'll upsert it |
482
|
|
|
$upsert = true; |
483
|
|
|
} |
484
|
|
|
|
485
|
|
|
// handle missing 'id' field in input to a PUT operation |
486
|
|
|
// if it is settable on the document, let's set it and move on.. if not, inform the user.. |
487
|
|
|
if ($record->getId() != $id) { |
488
|
|
|
// try to set it.. |
489
|
|
|
if (is_callable(array($record, 'setId'))) { |
490
|
|
|
$record->setId($id); |
491
|
|
|
} else { |
492
|
|
|
throw new MalformedInputException('No ID was supplied in the request payload.'); |
493
|
|
|
} |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
// And update the record, if everything is ok |
497
|
|
|
if ($upsert) { |
498
|
|
|
$this->getModel()->insertRecord($record); |
499
|
|
|
} else { |
500
|
|
|
$this->getModel()->updateRecord($id, $record); |
501
|
|
|
} |
502
|
|
|
|
503
|
|
|
// Set status code |
504
|
|
|
$response->setStatusCode(Response::HTTP_NO_CONTENT); |
505
|
|
|
|
506
|
|
|
// store id of new record so we dont need to reparse body later when needed |
507
|
|
|
$request->attributes->set('id', $record->getId()); |
508
|
|
|
|
509
|
|
|
return $response; |
510
|
|
|
} |
511
|
|
|
|
512
|
|
|
/** |
513
|
|
|
* Patch a record |
514
|
|
|
* |
515
|
|
|
* @param Number $id ID of record |
516
|
|
|
* @param Request $request Current http request |
517
|
|
|
* |
518
|
|
|
* @throws MalformedInputException |
519
|
|
|
* |
520
|
|
|
* @return Response $response Result of action with data (if successful) |
521
|
|
|
*/ |
522
|
|
|
public function patchAction($id, Request $request) |
523
|
|
|
{ |
524
|
|
|
$response = $this->getResponse(); |
525
|
|
|
$this->formValidator->checkJsonRequest($request, $response); |
526
|
|
|
|
527
|
|
|
// Check JSON Patch request |
528
|
|
|
$this->formValidator->checkJsonPatchRequest(json_decode($request->getContent(), 1)); |
529
|
|
|
|
530
|
|
|
// Find record && apply $ref converter |
531
|
|
|
$record = $this->findRecord($id); |
532
|
|
|
$jsonDocument = $this->serialize($record); |
533
|
|
|
|
534
|
|
|
// Check/validate JSON Patch |
535
|
|
|
if (!$this->jsonPatchValidator->validate($jsonDocument, $request->getContent())) { |
|
|
|
|
536
|
|
|
throw new InvalidJsonPatchException($this->jsonPatchValidator->getException()->getMessage()); |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
try { |
540
|
|
|
// Apply JSON patches |
541
|
|
|
$patch = new Patch($jsonDocument, $request->getContent()); |
|
|
|
|
542
|
|
|
$patchedDocument = $patch->apply(); |
543
|
|
|
} catch (InvalidPatchDocumentJsonException $e) { |
544
|
|
|
throw new InvalidJsonPatchException($e->getMessage()); |
545
|
|
|
} catch (InvalidTargetDocumentJsonException $e) { |
546
|
|
|
throw new InvalidJsonPatchException($e->getMessage()); |
547
|
|
|
} catch (InvalidOperationException $e) { |
548
|
|
|
throw new InvalidJsonPatchException($e->getMessage()); |
549
|
|
|
} catch (FailedTestException $e) { |
550
|
|
|
throw new InvalidJsonPatchException($e->getMessage()); |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
// Validate result object |
554
|
|
|
$model = $this->getModel(); |
555
|
|
|
$record = $this->formValidator->checkForm( |
556
|
|
|
$this->formValidator->getForm($request, $model), |
557
|
|
|
$model, |
558
|
|
|
$this->formDataMapper, |
559
|
|
|
$patchedDocument |
560
|
|
|
); |
561
|
|
|
|
562
|
|
|
// Update object |
563
|
|
|
$this->getModel()->updateRecord($id, $record); |
564
|
|
|
|
565
|
|
|
// Set status code |
566
|
|
|
$response->setStatusCode(Response::HTTP_OK); |
567
|
|
|
|
568
|
|
|
// Set Content-Location header |
569
|
|
|
$response->headers->set( |
570
|
|
|
'Content-Location', |
571
|
|
|
$this->getRouter()->generate($this->getRouteName($request), array('id' => $record->getId())) |
572
|
|
|
); |
573
|
1 |
|
|
574
|
|
|
return $response; |
575
|
1 |
|
} |
576
|
|
|
|
577
|
|
|
/** |
578
|
1 |
|
* Deletes a record |
579
|
|
|
* |
580
|
1 |
|
* @param Number $id ID of record |
581
|
1 |
|
* |
582
|
|
|
* @return Response $response Result of the action |
583
|
1 |
|
*/ |
584
|
|
|
public function deleteAction($id) |
585
|
|
|
{ |
586
|
|
|
$response = $this->getResponse(); |
587
|
|
|
|
588
|
|
|
// does this record exist? |
589
|
|
|
$this->findRecord($id); |
590
|
|
|
|
591
|
|
|
$this->getModel()->deleteRecord($id); |
592
|
|
|
$response->setStatusCode(Response::HTTP_NO_CONTENT); |
593
|
|
|
|
594
|
|
|
return $response; |
595
|
|
|
} |
596
|
|
|
|
597
|
|
|
/** |
598
|
|
|
* Return OPTIONS results. |
599
|
|
|
* |
600
|
|
|
* @param Request $request Current http request |
601
|
|
|
* |
602
|
|
|
* @throws SerializationException |
603
|
|
|
* @return \Symfony\Component\HttpFoundation\Response $response Result of the action |
604
|
|
|
*/ |
605
|
|
|
public function optionsAction(Request $request) |
606
|
|
|
{ |
607
|
|
|
list($app, $module, , $modelName) = explode('.', $request->attributes->get('_route')); |
608
|
|
|
|
609
|
|
|
$response = $this->response; |
610
|
|
|
$response->setStatusCode(Response::HTTP_OK); |
611
|
|
|
|
612
|
|
|
// enabled methods for CorsListener |
613
|
|
|
$corsMethods = 'GET, POST, PUT, PATCH, DELETE, OPTIONS'; |
614
|
|
|
try { |
615
|
|
|
$router = $this->getRouter(); |
616
|
|
|
// if post route is available we assume everything is readable |
617
|
|
|
$router->generate(implode('.', array($app, $module, 'rest', $modelName, 'post'))); |
618
|
|
|
} catch (RouteNotFoundException $exception) { |
619
|
|
|
// only allow read methods |
620
|
|
|
$corsMethods = 'GET, OPTIONS'; |
621
|
|
|
} |
622
|
|
|
$request->attributes->set('corsMethods', $corsMethods); |
623
|
|
|
|
624
|
|
|
return $response; |
625
|
|
|
} |
626
|
|
|
|
627
|
|
|
|
628
|
|
|
/** |
629
|
|
|
* Return schema GET results. |
630
|
|
|
* |
631
|
|
|
* @param Request $request Current http request |
632
|
|
|
* @param string $id ID of record |
633
|
|
|
* |
634
|
|
|
* @throws SerializationException |
635
|
|
|
* @return \Symfony\Component\HttpFoundation\Response $response Result of the action |
636
|
|
|
*/ |
637
|
|
|
public function schemaAction(Request $request, $id = null) |
638
|
|
|
{ |
639
|
|
|
$request->attributes->set('schemaRequest', true); |
640
|
|
|
|
641
|
|
|
list($app, $module, , $modelName, $schemaType) = explode('.', $request->attributes->get('_route')); |
642
|
|
|
|
643
|
|
|
$response = $this->response; |
644
|
|
|
$response->setStatusCode(Response::HTTP_OK); |
645
|
|
|
$response->setPublic(); |
646
|
|
|
|
647
|
|
|
if (!$id && $schemaType != 'canonicalIdSchema') { |
|
|
|
|
648
|
|
|
$schema = $this->schemaUtils->getCollectionSchema($modelName, $this->getModel()); |
649
|
|
|
} else { |
650
|
|
|
$schema = $this->schemaUtils->getModelSchema($modelName, $this->getModel()); |
651
|
|
|
} |
652
|
|
|
|
653
|
|
|
// enabled methods for CorsListener |
654
|
|
|
$corsMethods = 'GET, POST, PUT, PATCH, DELETE, OPTIONS'; |
655
|
|
|
try { |
656
|
|
|
$router = $this->getRouter(); |
657
|
|
|
// if post route is available we assume everything is readable |
658
|
|
|
$router->generate(implode('.', array($app, $module, 'rest', $modelName, 'post'))); |
659
|
|
|
} catch (RouteNotFoundException $exception) { |
660
|
|
|
// only allow read methods |
661
|
|
|
$corsMethods = 'GET, OPTIONS'; |
662
|
|
|
} |
663
|
|
|
$request->attributes->set('corsMethods', $corsMethods); |
664
|
|
|
|
665
|
|
|
return $this->render( |
666
|
|
|
'GravitonRestBundle:Main:index.json.twig', |
667
|
|
|
['response' => $this->serialize($schema)], |
668
|
|
|
$response |
669
|
|
|
); |
670
|
|
|
} |
671
|
|
|
|
672
|
|
|
/** |
673
|
|
|
* Get the validator |
674
|
|
|
* |
675
|
|
|
* @return ValidatorInterface |
676
|
|
|
*/ |
677
|
|
|
public function getValidator() |
678
|
|
|
{ |
679
|
|
|
return $this->validator; |
680
|
2 |
|
} |
681
|
|
|
|
682
|
2 |
|
/** |
683
|
|
|
* Renders a view. |
684
|
|
|
* |
685
|
|
|
* @param string $view The view name |
686
|
|
|
* @param array $parameters An array of parameters to pass to the view |
687
|
|
|
* @param Response $response A response instance |
688
|
|
|
* |
689
|
|
|
* @return Response A Response instance |
690
|
|
|
*/ |
691
|
|
|
public function render($view, array $parameters = array(), Response $response = null) |
692
|
|
|
{ |
693
|
|
|
return $this->templating->renderResponse($view, $parameters, $response); |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
/** |
697
|
|
|
* @param Request $request request |
698
|
|
|
* @return string |
699
|
|
|
*/ |
700
|
|
|
private function getRouteName(Request $request) |
701
|
|
|
{ |
702
|
|
|
$routeName = $request->get('_route'); |
703
|
|
|
$routeParts = explode('.', $routeName); |
704
|
|
|
$routeType = end($routeParts); |
705
|
|
|
|
706
|
|
|
if ($routeType == 'post') { |
707
|
1 |
|
$routeName = substr($routeName, 0, -4) . 'get'; |
708
|
|
|
} |
709
|
1 |
|
|
710
|
1 |
|
return $routeName; |
711
|
|
|
} |
712
|
|
|
|
713
|
|
|
/** |
714
|
|
|
* Security needs to be enabled to get Object. |
715
|
|
|
* |
716
|
|
|
* @return SecurityUser |
717
|
|
|
* @throws PreconditionRequiredHttpException |
718
|
|
|
*/ |
719
|
|
|
public function getSecurityUser() |
720
|
|
|
{ |
721
|
|
|
/** @var PreAuthenticatedToken $token */ |
722
|
|
|
if ($token = $this->tokenStorage->getToken()) { |
723
|
|
|
return $token->getUser(); |
724
|
|
|
} |
725
|
|
|
|
726
|
|
|
throw new PreconditionRequiredHttpException('Not allowed'); |
727
|
|
|
} |
728
|
|
|
} |
729
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.