Completed
Push — standalone ( 83bb11...ac8c4f )
by Philip
03:51
created

EntityController::listSubresource()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 4
crap 1
1
<?php
2
3
namespace Dontdrinkandroot\RestBundle\Controller;
4
5
use Doctrine\Common\Util\Inflector;
6
use Doctrine\ORM\Tools\Pagination\Paginator;
7
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Right;
8
use Dontdrinkandroot\RestBundle\Metadata\ClassMetadata;
9
use Dontdrinkandroot\RestBundle\Metadata\PropertyMetadata;
10
use Dontdrinkandroot\RestBundle\Service\CrudServiceInterface;
11
use Dontdrinkandroot\RestBundle\Service\DoctrineEntityRepositoryCrudService;
12
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
13
use Symfony\Component\HttpFoundation\JsonResponse;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\Response;
16
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
17
use Symfony\Component\Validator\ConstraintViolationInterface;
18
use Symfony\Component\Validator\ConstraintViolationListInterface;
19
use Symfony\Component\Validator\Validator\ValidatorInterface;
20
21
class EntityController extends Controller
22
{
23 6
    public function listAction(Request $request)
24
    {
25 6
        $page = $request->query->get('page', 1);
26 6
        $perPage = $request->query->get('perPage', 50);
27
28 6
        $this->assertListGranted();
29
30 4
        $paginator = $this->listEntities($page, $perPage);
31 4
        $total = $paginator->count();
32 4
        $entities = $paginator->getIterator()->getArrayCopy();
33
34 4
        $normalizer = $this->get('ddr_rest.normalizer');
35 4
        $content = $normalizer->normalize($entities, $this->parseIncludes($request));
36
37 4
        $response = new JsonResponse($content, Response::HTTP_OK);
38 4
        $this->addPaginationHeaders($response, $page, $perPage, $total);
39
40 4
        return $response;
41
    }
42
43 2
    public function postAction(Request $request)
44
    {
45 2
        $this->assertPostGranted();
46
        $entity = $this->parseRequest($request, null, $this->getEntityClass());
47
        $entity = $this->postProcessPostedEntity($entity);
48
49
        /** @var ValidatorInterface $validator */
50
        $validator = $this->get('validator');
51
        $errors = $validator->validate($entity);
52
        if ($errors->count() > 0) {
53
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
54
        }
55
56
        $entity = $this->createEntity($entity);
57
58
        $normalizer = $this->get('ddr_rest.normalizer');
59
        $content = $normalizer->normalize($entity);
60
61
        return new JsonResponse($content, Response::HTTP_CREATED);
62
    }
63
64 8
    public function getAction(Request $request, $id)
65
    {
66 8
        $entity = $this->fetchEntity($id);
67 8
        $this->assertGetGranted($entity);
68
69 6
        $normalizer = $this->get('ddr_rest.normalizer');
70 6
        $content = $normalizer->normalize($entity, $this->parseIncludes($request));
71
72 6
        return new JsonResponse($content);
73
    }
74
75 6
    public function putAction(Request $request, $id)
76
    {
77 6
        $entity = $this->fetchEntity($id);
78 6
        $this->assertPutGranted($entity);
79 2
        $entity = $this->parseRequest($request, $entity, $this->getEntityClass());
80 2
        $entity = $this->postProcessPuttedEntity($entity);
81
82
        /** @var ValidatorInterface $validator */
83 2
        $validator = $this->get('validator');
84 2
        $errors = $validator->validate($entity);
85 2
        if ($errors->count() > 0) {
86
            return new JsonResponse($this->parseConstraintViolations($errors), Response::HTTP_BAD_REQUEST);
87
        }
88
89 2
        $entity = $this->updateEntity($entity);
90
91 2
        $normalizer = $this->get('ddr_rest.normalizer');
92 2
        $content = $normalizer->normalize($entity);
93
94 2
        return new JsonResponse($content);
95
    }
96
97 2
    public function deleteAction(Request $request, $id)
0 ignored issues
show
Unused Code introduced by
The parameter $request 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...
98
    {
99 2
        $entity = $this->fetchEntity($id);
100 2
        $this->assertDeleteGranted($entity);
101
        $this->getService()->remove($entity);
102
103
        return new JsonResponse(null, Response::HTTP_NO_CONTENT);
104
    }
105
106 2
    public function listSubresourceAction(Request $request, $id)
107
    {
108 2
        $page = $request->query->get('page', 1);
109 2
        $perPage = $request->query->get('perPage', 50);
110
111 2
        $subresource = $this->getSubresource();
112 2
        $entity = $this->fetchEntity($id);
113 2
        $this->assertSubresourceListGranted($entity, $subresource);
114
115 2
        $paginator = $this->listSubresource(
116
            $entity,
117
            $subresource,
118
            $page,
119
            $perPage
120
        );
121 2
        $total = $paginator->count();
122 2
        $entities = $paginator->getIterator()->getArrayCopy();
123
124 2
        $normalizer = $this->get('ddr_rest.normalizer');
125 2
        $content = $normalizer->normalize($entities, $this->parseIncludes($request));
126
127 2
        $response = new JsonResponse($content, Response::HTTP_OK);
128 2
        $this->addPaginationHeaders($response, $page, $perPage, $total);
129
130 2
        return $response;
131
    }
132
133
    public function postSubresourceAction(Request $request, $id)
134
    {
135
        $subresource = $this->getSubresource();
136
        $parent = $this->fetchEntity($id);
137
        $this->assertSubresourcePostGranted($parent, $subresource);
138
        $entity = $this->parseRequest($request, null, $this->getSubResourceEntityClass($subresource));
139
        $entity = $this->postProcessSubResourcePostedEntity($subresource, $entity, $parent);
140
        $errors = $this->validate($entity);
0 ignored issues
show
Bug introduced by
The method validate() does not seem to exist on object<Dontdrinkandroot\...oller\EntityController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
141
        if ($errors->count() > 0) {
142
            return $this->handleView($this->view($errors, Response::HTTP_BAD_REQUEST));
0 ignored issues
show
Bug introduced by
The method view() does not seem to exist on object<Dontdrinkandroot\...oller\EntityController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method handleView() does not seem to exist on object<Dontdrinkandroot\...oller\EntityController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
143
        }
144
        $entity = $this->saveSubResource($subresource, $entity);
145
146
        return $this->handleView($this->view($entity, Response::HTTP_CREATED));
0 ignored issues
show
Bug introduced by
The method view() does not seem to exist on object<Dontdrinkandroot\...oller\EntityController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method handleView() does not seem to exist on object<Dontdrinkandroot\...oller\EntityController>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
147
    }
148
149
    /**
150
     * @return CrudServiceInterface
151
     */
152 22
    protected function getService()
153
    {
154 22
        $serviceId = $this->getServiceId();
155 22
        if (null === $serviceId) {
156 22
            $entityClass = $this->getEntityClass();
157 22
            if (null === $entityClass) {
158
                throw new \RuntimeException('No service or entity class given');
159
            }
160 22
            $entityManager = $this->get('doctrine.orm.entity_manager');
161
162 22
            return new DoctrineEntityRepositoryCrudService(
163
                $entityManager,
164
                $entityClass
165
            );
166
        } else {
167
            /** @var CrudServiceInterface $service */
168
            $service = $this->get($serviceId);
169
170
            return $service;
171
        }
172
    }
173
174 2
    protected function parseRequest(Request $request, $entity = null, $entityClass = null)
175
    {
176 2
        return $this->get('ddr.rest.parser.request')->parseEntity($request, $entityClass, $entity);
177
    }
178
179
    /**
180
     * @param object $entity
181
     *
182
     * @return object
183
     */
184
    protected function postProcessPostedEntity($entity)
185
    {
186
        return $entity;
187
    }
188
189
    /**
190
     * @param object $entity
191
     *
192
     * @return object
193
     */
194 2
    protected function postProcessPuttedEntity($entity)
195
    {
196 2
        return $entity;
197
    }
198
199
    /**
200
     * @param string $subresource
201
     * @param object $parent
202
     * @param object $entity
203
     *
204
     * @return object
205
     */
206
    protected function postProcessSubResourcePostedEntity($subresource, $entity, $parent)
2 ignored issues
show
Unused Code introduced by
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...
Unused Code introduced by
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...
207
    {
208
        return $entity;
209
    }
210
211 18
    protected function fetchEntity($id)
212
    {
213 18
        $entity = $this->getService()->findById($id);
214 18
        if (null === $entity) {
215
            throw new NotFoundHttpException();
216
        }
217
218 18
        return $entity;
219
    }
220
221 4
    protected function listEntities(int $page = 1, int $perPage = 50): Paginator
222
    {
223 4
        $service = $this->getService();
224
225 4
        return $service->listPaginated($page, $perPage);
226
    }
227
228
    protected function createEntity($entity)
229
    {
230
        return $this->getService()->create($entity);
231
    }
232
233 2
    protected function updateEntity($entity)
234
    {
235 2
        return $this->getService()->update($entity);
236
    }
237
238 2
    protected function listSubresource($entity, $property, $page = 1, $perPage = 50): Paginator
239
    {
240 2
        $service = $this->getService();
241
242 2
        return $service->listAssociationPaginated($entity, $property, $page, $perPage);
243
    }
244
245 26
    protected function getEntityClass()
246
    {
247 26
        return $this->getCurrentRequest()->attributes->get('_entityClass');
248
    }
249
250
    protected function getShortName()
251
    {
252
        return Inflector::tableize($this->getClassMetadata()->reflection->getShortName());
253
    }
254
255 22
    protected function getServiceId()
256
    {
257 22
        return $this->getCurrentRequest()->attributes->get('_service');
258
    }
259
260 26
    protected function getCurrentRequest()
261
    {
262 26
        return $this->get('request_stack')->getCurrentRequest();
263
    }
264
265 6
    protected function assertListGranted()
266
    {
267 6
        $classMetadata = $this->getClassMetadata();
268 6
        $right = $classMetadata->getListRight();
269 6
        if (null === $right) {
270 2
            return;
271
        }
272
273 4
        $this->denyAccessUnlessGranted($right->attributes);
274 2
    }
275
276 2
    protected function assertPostGranted()
277
    {
278 2
        $classMetadata = $this->getClassMetadata();
279 2
        $right = $classMetadata->getPostRight();
280 2
        if (null === $right) {
281 2
            throw $this->createAccessDeniedException();
282
        }
283
284
        $this->denyAccessUnlessGranted($right->attributes);
285
    }
286
287 8
    protected function assertGetGranted($entity)
288
    {
289 8
        $classMetadata = $this->getClassMetadata();
290 8
        $right = $classMetadata->getGetRight();
291 8
        if (null === $right) {
292 2
            return;
293
        }
294
295 6
        $this->assertRightGranted($entity, $right);
296 4
    }
297
298 6
    protected function assertPutGranted($entity)
299
    {
300 6
        $classMetadata = $this->getClassMetadata();
301 6
        $right = $classMetadata->getPutRight();
302 6
        if (null === $right) {
303 2
            throw $this->createAccessDeniedException();
304
        }
305
306 4
        $this->assertRightGranted($entity, $right);
307 2
    }
308
309 2
    protected function assertDeleteGranted($entity)
310
    {
311 2
        $classMetadata = $this->getClassMetadata();
312 2
        $right = $classMetadata->getDeleteRight();
313 2
        if (null === $right) {
314 2
            throw $this->createAccessDeniedException();
315
        }
316
317
        $this->assertRightGranted($entity, $right);
318
    }
319
320 2
    protected function assertSubresourceListGranted($entity, $subresource)
321
    {
322 2
        $classMetadata = $this->getClassMetadata();
323
        /** @var PropertyMetadata $propertyMetadata */
324 2
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
325 2
        $right = $propertyMetadata->getSubResourceListRight();
326 2
        if (null === $right) {
327 2
            return;
328
        }
329
330
        $this->assertRightGranted($entity, $right);
331
    }
332
333
    protected function assertSubresourcePostGranted($entity, $subresource)
334
    {
335
        $classMetadata = $this->getClassMetadata();
336
        /** @var PropertyMetadata $propertyMetadata */
337
        $propertyMetadata = $classMetadata->propertyMetadata[$subresource];
338
        $right = $propertyMetadata->getSubResourcePostRight();
339
        if (null === $right) {
340
            throw $this->createAccessDeniedException();
341
        }
342
343
        $this->assertRightGranted($entity, $right);
344
    }
345
346
    /**
347
     * @return ClassMetadata
348
     */
349 26
    protected function getClassMetadata()
350
    {
351 26
        $metaDataFactory = $this->get('ddr_rest.metadata.factory');
352
        /** @var ClassMetadata $classMetaData */
353 26
        $classMetaData = $metaDataFactory->getMetadataForClass($this->getEntityClass());
354
355 26
        return $classMetaData;
356
    }
357
358
    protected function getSubResourceEntityClass($subresource)
359
    {
360
        /** @var PropertyMetadata $propertyMetadata */
361
        $propertyMetadata = $this->getClassMetadata()->propertyMetadata[$subresource];
362
363
        return $propertyMetadata->getTargetClass();
364
    }
365
366
    protected function resolveSubject($entity, $propertyPath)
367
    {
368
        if ('this' === $propertyPath) {
369
            return $entity;
370
        }
371
        $propertyAccessor = $this->get('property_accessor');
372
373
        return $propertyAccessor->getValue($entity, $propertyPath);
374
    }
375
376
    /**
377
     * @param object $entity
378
     * @param Right  $right
379
     */
380 10
    protected function assertRightGranted($entity, Right $right)
381
    {
382 10
        $propertyPath = $right->propertyPath;
383 10
        if (null === $propertyPath) {
384 10
            $this->denyAccessUnlessGranted($right->attributes);
385
        } else {
386
            $subject = $this->resolveSubject($entity, $propertyPath);
387
            $this->denyAccessUnlessGranted($right->attributes, $subject);
388
        }
389 6
    }
390
391
    /**
392
     * @return string[]
393
     */
394
    protected function getSubresourceSerializationGroups($subresource)
395
    {
396
        return ['Default', 'ddr.rest.subresource', 'ddr.rest.' . $this->getShortName() . '.' . $subresource];
397
    }
398
399
    /**
400
     * @param string $subresource
401
     * @param object $entity
402
     *
403
     * @return
404
     */
405
    protected function saveSubResource($subresource, $entity)
0 ignored issues
show
Unused Code introduced by
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...
406
    {
407
        return $this->getService()->save($entity);
0 ignored issues
show
Bug introduced by
The method save() does not seem to exist on object<Dontdrinkandroot\...e\CrudServiceInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
408
    }
409
410
    /**
411
     * @return string|null
412
     */
413 2
    protected function getSubresource()
414
    {
415 2
        return $this->getCurrentRequest()->attributes->get('_subresource');
416
    }
417
418 12
    protected function parseIncludes(Request $request)
419
    {
420 12
        $includeString = $request->query->get('include');
421 12
        if (empty($includeString)) {
422 10
            return [];
423
        }
424
425 2
        return explode(',', $includeString);
426
    }
427
428
    private function parseConstraintViolations(ConstraintViolationListInterface $errors)
429
    {
430
        $data = [];
431
        /** @var ConstraintViolationInterface $error */
432
        foreach ($errors as $error) {
433
            $data[] = [
434
                'propertyPath' => $error->getPropertyPath(),
435
                'message'      => $error->getMessage(),
436
                'value'        => $error->getInvalidValue()
437
            ];
438
        }
439
440
        return $data;
441
    }
442
443 6
    private function addPaginationHeaders(Response $response, int $page, int $perPage, int $total)
444
    {
445 6
        $response->headers->add(
446
            [
447 6
                'x-pagination-current-page' => $page,
448 6
                'x-pagination-per-page'     => $perPage,
449 6
                'x-pagination-total'        => $total,
450 6
                'x-pagination-total-pages'  => (int)(($total - 1) / $perPage + 1)
451
            ]
452
        );
453 6
    }
454
}
455