Completed
Push — master ( b0a6d2...faed70 )
by Philip
09:15
created

Controller/AbstractRestResourceController.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Dontdrinkandroot\RestBundle\Controller;
4
5
use Doctrine\ORM\Tools\Pagination\Paginator;
6
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Method;
7
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Right;
8
use Dontdrinkandroot\RestBundle\Metadata\ClassMetadata;
9
use Dontdrinkandroot\RestBundle\Metadata\PropertyMetadata;
10
use Dontdrinkandroot\RestBundle\Service\Normalizer;
11
use Dontdrinkandroot\RestBundle\Service\RestRequestParser;
12
use Metadata\MetadataFactoryInterface;
13
use Symfony\Component\HttpFoundation\JsonResponse;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\RequestStack;
16
use Symfony\Component\HttpFoundation\Response;
17
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
18
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
19
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
20
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
21
use Symfony\Component\Validator\ConstraintViolationInterface;
22
use Symfony\Component\Validator\ConstraintViolationListInterface;
23
use Symfony\Component\Validator\Validator\ValidatorInterface;
24
25
/**
26
 * @author Philip Washington Sorst <[email protected]>
27
 */
28
abstract class AbstractRestResourceController implements RestResourceControllerInterface
29
{
30
    /**
31
     * {@inheritdoc}
32
     */
33 10
    public function listAction(Request $request)
34
    {
35 10
        $page = $request->query->get('page', 1);
36 10
        $perPage = $request->query->get('perPage', 50);
37
38 10
        $this->assertMethodGranted(Method::LIST);
39
40 6
        $listResult = $this->listEntities($page, $perPage);
41
42 6
        $response = new JsonResponse();
43
44 6
        if ($listResult instanceof Paginator) {
45 6
            $entities = iterator_to_array($listResult->getIterator());
46 6
            $total = $listResult->count();
47 6
            $this->addPaginationHeaders($response, $page, $perPage, $total);
48
        } else {
49
            $entities = $listResult;
50
        }
51
52 6
        $content = $this->getNormalizer()->normalize($entities, $this->parseIncludes($request));
53
54 6
        $response->setData($content);
55
56 6
        return $response;
57
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62 14
    public function postAction(Request $request)
63
    {
64 14
        $this->assertMethodGranted(Method::POST);
65
66 12
        $entity = $this->getRequestParser()->parseEntity($request, $this->getEntityClass());
67 12
        $entity = $this->postProcessPostedEntity($entity);
68
69 12
        $errors = $this->getValidator()->validate($entity);
70 12
        if ($errors->count() > 0) {
71 2
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
72
        }
73
74 10
        $entity = $this->createEntity($entity);
75
76 10
        $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request));
77
78 10
        return new JsonResponse($content, Response::HTTP_CREATED);
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84 32
    public function getAction(Request $request, $id)
85
    {
86 32
        $entity = $this->fetchEntity($id);
87 30
        $this->assertMethodGranted(Method::GET, $entity);
88
89 28
        $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request));
90
91 28
        return new JsonResponse($content);
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     */
97 12
    public function putAction(Request $request, $id)
98
    {
99 12
        $entity = $this->fetchEntity($id);
100 12
        $this->assertMethodGranted(Method::PUT, $entity);
101 10
        $entity = $this->getRequestParser()->parseEntity($request, $this->getEntityClass(), $entity);
102 10
        $entity = $this->postProcessPuttedEntity($entity);
103
104 10
        $errors = $this->getValidator()->validate($entity);
105 10
        if ($errors->count() > 0) {
106
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
107
        }
108
109 10
        $entity = $this->updateEntity($entity);
110
111 10
        $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request));
112
113 10
        return new JsonResponse($content);
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119 4
    public function deleteAction(Request $request, $id)
120
    {
121 4
        $entity = $this->fetchEntity($id);
122 4
        $this->assertMethodGranted(Method::DELETE, $entity);
123 2
        $this->removeEntity($entity);
124
125 2
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131 6
    public function listSubresourceAction(Request $request, $id, string $subresource)
132
    {
133 6
        $page = $request->query->get('page', 1);
134 6
        $perPage = $request->query->get('perPage', 50);
135
136 6
        $entity = $this->fetchEntity($id);
137 6
        $this->assertSubResourceMethodGranted(Method::LIST, $entity, $subresource);
138
139 6
        $listResult = $this->listSubresource($entity, $subresource, $page, $perPage);
140
141 6
        $response = new JsonResponse();
142
143 6
        if ($listResult instanceof Paginator) {
144 6
            $entities = iterator_to_array($listResult->getIterator());
145 6
            $total = $listResult->count();
146 6
            $this->addPaginationHeaders($response, $page, $perPage, $total);
147
        } else {
148
            $entities = $listResult;
149
        }
150
151 6
        $content = $this->getNormalizer()->normalize($entities, $this->parseIncludes($request));
152
153 6
        $response->setData($content);
154
155 6
        return $response;
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161 4
    public function postSubresourceAction(Request $request, $id, string $subresource)
162
    {
163 4
        $parent = $this->fetchEntity($id);
164 4
        $this->assertSubResourceMethodGranted(Method::POST, $parent, $subresource);
165
166 2
        $restRequestParser = $this->getRequestParser();
167 2
        $entity = $this->createAssociation($parent, $subresource);
168 2
        $entity = $restRequestParser->parseEntity($request, $this->getSubResourceEntityClass($subresource), $entity);
169
170 2
        $entity = $this->postProcessSubResourcePostedEntity($parent, $subresource, $entity);
171
172 2
        $errors = $this->getValidator()->validate($entity);
173
174 2
        if ($errors->count() > 0) {
175
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
176
        }
177
178 2
        $entity = $this->createSubResource($parent, $subresource, $entity);
179
180 2
        $content = $this->getNormalizer()->normalize($entity, $this->parseIncludes($request));
181
182 2
        return new JsonResponse($content, Response::HTTP_CREATED);
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188 12
    public function putSubresourceAction(Request $request, $id, string $subresource, $subId)
189
    {
190 12
        $parent = $this->fetchEntity($id);
191 12
        $this->assertSubResourceMethodGranted(Method::PUT, $parent, $subresource);
192 12
        $this->addAssociation($parent, $subresource, $subId);
193
194 12
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200 12
    public function deleteSubresourceAction(Request $request, $id, string $subresource, $subId = null)
201
    {
202 12
        $parent = $this->fetchEntity($id);
203 12
        $this->assertSubResourceMethodGranted(Method::DELETE, $parent, $subresource);
204 12
        $this->removeAssociation($parent, $subresource, $subId);
205
206 12
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
207
    }
208
209
    /**
210
     * @param object $entity
211
     *
212
     * @return object
213
     */
214 12
    protected function postProcessPostedEntity($entity)
215
    {
216 12
        return $entity;
217
    }
218
219
    /**
220
     * @param object $entity
221
     *
222
     * @return object
223
     */
224 10
    protected function postProcessPuttedEntity($entity)
225
    {
226 10
        return $entity;
227
    }
228
229
    /**
230
     * @param object $parent
231
     * @param string $subresource
232
     * @param object $entity
233
     *
234
     * @return object
235
     */
236 2
    protected function postProcessSubResourcePostedEntity($parent, $subresource, $entity)
0 ignored issues
show
The parameter $parent is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $subresource is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
237
    {
238 2
        return $entity;
239
    }
240
241 80
    protected function getEntityClass()
242
    {
243 80
        return $this->getCurrentRequest()->attributes->get('_entityClass');
244
    }
245
246 2
    protected function getSubResourceEntityClass($subresource)
247
    {
248
        /** @var PropertyMetadata $propertyMetadata */
249 2
        $propertyMetadata = $this->getClassMetadata()->propertyMetadata[$subresource];
250
251 2
        return $propertyMetadata->getType();
252
    }
253
254 80
    protected function getCurrentRequest()
255
    {
256 80
        return $this->getRequestStack()->getCurrentRequest();
257
    }
258
259 70
    protected function assertMethodGranted(string $methodName, $entity = null)
260
    {
261 70
        $method = $this->getClassMetadata()->getMethod($methodName);
262 70
        if ($method !== null && null !== $right = $method->right) {
263 36
            $this->assertRightGranted($right, $entity);
264
        }
265 58
    }
266
267
    /**
268
     * @param string $methodName
269
     * @param object $entity
270
     * @param string $subresource
271
     */
272 30
    protected function assertSubResourceMethodGranted($methodName, $entity, string $subresource): void
273
    {
274 30
        $classMetadata = $this->getClassMetadata();
275
        /** @var PropertyMetadata $propertyMetadata */
276 30
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
277 30
        $method = $propertyMetadata->getMethod($methodName);
278 30
        if (null !== $right = $method->right) {
279 14
            $this->assertRightGranted($right, $entity);
280
        }
281 28
    }
282
283
    /**
284
     * @return ClassMetadata
285
     */
286 80
    protected function getClassMetadata()
287
    {
288 80
        $metaDataFactory = $this->getMetadataFactory();
289
        /** @var ClassMetadata $classMetaData */
290 80
        $classMetaData = $metaDataFactory->getMetadataForClass($this->getEntityClass());
291
292 80
        return $classMetaData;
293
    }
294
295
    protected function resolveSubject($entity, $propertyPath)
296
    {
297
        if ('this' === $propertyPath) {
298
            return $entity;
299
        }
300
        $propertyAccessor = $this->getPropertyAccessor();
301
302
        return $propertyAccessor->getValue($entity, $propertyPath);
303
    }
304
305
    /**
306
     * @param Right  $right
307
     * @param object $entity
308
     */
309 46
    protected function assertRightGranted(Right $right, $entity = null)
310
    {
311 46
        $propertyPath = $right->propertyPath;
312 46
        if (null === $propertyPath || null == $entity) {
313 46
            $this->denyAccessUnlessGranted($right->attributes);
314
        } else {
315
            $subject = $this->resolveSubject($entity, $propertyPath);
316
            $this->denyAccessUnlessGranted($right->attributes, $subject);
317
        }
318 32
    }
319
320 62
    protected function parseIncludes(Request $request)
321
    {
322 62
        $defaultIncludes = $request->attributes->get('_defaultincludes');
323 62
        if (null == $defaultIncludes) {
324 30
            $defaultIncludes = [];
325
        }
326
327 62
        $includeString = $request->query->get('include');
328 62
        if (empty($includeString)) {
329 48
            $includes = [];
330
        } else {
331 16
            $includes = explode(',', $includeString);
332
        }
333
334 62
        return array_merge($defaultIncludes, $includes);
335
    }
336
337 46
    protected function denyAccessUnlessGranted($attributes, $object = null, $message = 'Access Denied.')
338
    {
339 46
        if (!$this->getAuthorizationChecker()->isGranted($attributes, $object)) {
340 14
            throw new AccessDeniedException($message);
341
        }
342 32
    }
343
344 2
    protected function parseConstraintViolations(ConstraintViolationListInterface $errors)
345
    {
346 2
        $data = [];
347
        /** @var ConstraintViolationInterface $error */
348 2
        foreach ($errors as $error) {
349 2
            $data[] = [
350 2
                'propertyPath' => $error->getPropertyPath(),
351 2
                'message'      => $error->getMessage(),
352 2
                'value'        => $error->getInvalidValue()
353
            ];
354
        }
355
356 2
        return $data;
357
    }
358
359 12
    protected function addPaginationHeaders(Response $response, int $page, int $perPage, int $total)
360
    {
361 12
        $response->headers->add(
362
            [
363 12
                'x-pagination-current-page' => $page,
364 12
                'x-pagination-per-page'     => $perPage,
365 12
                'x-pagination-total'        => $total,
366 12
                'x-pagination-total-pages'  => (int)(($total - 1) / $perPage + 1)
367
            ]
368
        );
369 12
    }
370
371
    /**
372
     * @param int $page
373
     * @param int $perPage
374
     *
375
     * @return Paginator|array
376
     */
377
    abstract protected function listEntities(int $page = 1, int $perPage = 50);
378
379
    /**
380
     * @param int|string $id
381
     *
382
     * @return object
383
     *
384
     * @throws NotFoundHttpException Thrown if entity with the given id could not be found.
385
     */
386
    abstract protected function fetchEntity($id);
387
388
    /**
389
     * @param object $entity
390
     *
391
     * @return object
392
     */
393
    abstract protected function createEntity($entity);
394
395
    /**
396
     * @param object $entity
397
     *
398
     * @return object
399
     *
400
     * @throws NotFoundHttpException Thrown if entity with the given id could not be found.
401
     */
402
    abstract protected function updateEntity($entity);
403
404
    /**
405
     * @param $entity
406
     *
407
     * @throws NotFoundHttpException Thrown if entity with the given id could not be found.
408
     */
409
    abstract protected function removeEntity($entity);
410
411
    /**
412
     * @param object $entity
413
     * @param string $subresource
414
     * @param int    $page
415
     * @param int    $perPage
416
     *
417
     * @return Paginator|array
418
     */
419
    abstract protected function listSubresource($entity, string $subresource, int $page = 1, int $perPage = 50);
420
421
    /**
422
     * @param object $parent
423
     * @param string $subresource
424
     *
425
     * @return object
426
     */
427
    abstract protected function createAssociation($parent, string $subresource);
428
429
    /**
430
     * @param object $parent
431
     * @param string $subresource
432
     * @param object $entity
433
     *
434
     * @return object
435
     */
436
    abstract protected function createSubResource($parent, $subresource, $entity);
437
438
    /**
439
     * @param object     $parent
440
     * @param string     $subresource
441
     * @param int|string $subId
442
     *
443
     * @return object
444
     */
445
    abstract protected function addAssociation($parent, string $subresource, $subId);
446
447
    /**
448
     * @param object          $parent
449
     * @param string          $subresource
450
     * @param int|string|null $subId
451
     *
452
     * @return mixed
453
     */
454
    abstract protected function removeAssociation($parent, string $subresource, $subId = null);
455
456
    /**
457
     * @return Normalizer
458
     */
459
    abstract protected function getNormalizer();
460
461
    /**
462
     * @return ValidatorInterface
463
     */
464
    abstract protected function getValidator();
465
466
    /**
467
     * @return RestRequestParser
468
     */
469
    abstract protected function getRequestParser();
470
471
    /**
472
     * @return RequestStack
473
     */
474
    abstract protected function getRequestStack();
475
476
    /**
477
     * @return MetadataFactoryInterface
478
     */
479
    abstract protected function getMetadataFactory();
480
481
    /**
482
     * @return PropertyAccessorInterface
483
     */
484
    abstract protected function getPropertyAccessor();
485
486
    /**
487
     * @return AuthorizationCheckerInterface
488
     */
489
    abstract protected function getAuthorizationChecker();
490
}
491