Passed
Push — master ( 7f0b07...1babeb )
by Daniel
07:09
created

ApiNormalizer   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 110
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 23
Bugs 3 Features 1
Metric Value
wmc 29
eloc 57
c 23
b 3
f 1
dl 0
loc 110
ccs 0
cts 22
cp 0
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A rolesVote() 0 14 6
B supportsNormalization() 0 25 8
B normalize() 0 23 8
A getId() 0 6 2
A __construct() 0 5 1
A isRestrictedResource() 0 6 3
A hasCacheableSupportsMethod() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Silverback\ApiComponentBundle\Serializer;
6
7
use Silverback\ApiComponentBundle\DataTransformer\DataTransformerInterface;
8
use Silverback\ApiComponentBundle\Entity\Component\AbstractComponent;
9
use Silverback\ApiComponentBundle\Entity\Content\AbstractContent;
10
use Silverback\ApiComponentBundle\Entity\Content\Page\Dynamic\DynamicContent;
11
use Silverback\ApiComponentBundle\Entity\RestrictedResourceInterface;
12
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
13
use Symfony\Component\PropertyAccess\PropertyAccess;
14
use Symfony\Component\Security\Core\Security;
15
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
16
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
17
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
18
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
19
20
class ApiNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface, CacheableSupportsMethodInterface
21
{
22
    use NormalizerAwareTrait;
23
24
    private const ALREADY_CALLED = 'API_COMPONENT_BUNDLE_NORMALIZER_ALREADY_CALLED';
25
26
    /** @var iterable|DataTransformerInterface[] */
27
    private $dataTransformers;
28
29
    /** @var DataTransformerInterface[] */
30
    private $supportedTransformers = [];
31
32
    private $security;
33
    private $propertyAccessor;
34
35
    public function __construct(iterable $dataTransformers = [], Security $security)
36
    {
37
        $this->dataTransformers = $dataTransformers;
38
        $this->security = $security;
39
        $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
40
    }
41
42
    private function isRestrictedResource($data): ?RestrictedResourceInterface
43
    {
44
        if ($data instanceof RestrictedResourceInterface && !$data instanceof AbstractContent) {
45
            return $data;
46
        }
47
        return null;
48
    }
49
50
    private function getId($object)
51
    {
52
        try {
53
            return $this->propertyAccessor->getValue($object, 'id');
54
        } catch (NoSuchPropertyException $e) {
55
            return true;
56
        }
57
    }
58
59
    public function supportsNormalization($data, $format = null, array $context = []): bool
60
    {
61
        if (!isset($context[self::ALREADY_CALLED])) {
62
            $context[self::ALREADY_CALLED] = [];
63
        }
64
        $this->supportedTransformers = [];
65
        if (!is_object($data) || in_array($this->getId($data), $context[self::ALREADY_CALLED], true)) {
66
            return false;
67
        }
68
69
        foreach ($this->dataTransformers as $transformer) {
70
            if ($transformer->supportsTransformation($data)) {
71
                $this->supportedTransformers[] = $transformer;
72
            }
73
        }
74
75
        if ($data instanceof AbstractComponent) {
76
            return true;
77
        }
78
79
        if ($this->isRestrictedResource($data)) {
80
            return true;
81
        }
82
83
        return !empty($this->supportedTransformers);
84
    }
85
86
    private function rolesVote(iterable $roles): bool
87
    {
88
        $negativeRoles = [];
89
        $positiveRoles = [];
90
        foreach ($roles as $role) {
91
            if (strpos($role, '!') === 0) {
92
                $negativeRoles[] = substr($role, 1);
93
                continue;
94
            }
95
            $positiveRoles[] = $role;
96
        }
97
        $positivePass = count($positiveRoles) && $this->security->isGranted($positiveRoles);
98
        $negativePass = count($negativeRoles) && !$this->security->isGranted($negativeRoles);
99
        return $positivePass || $negativePass;
100
    }
101
102
    public function normalize($object, $format = null, array $context = [])
103
    {
104
        if (
105
            ($restrictedResource = $this->isRestrictedResource($object)) &&
106
            ($roles = $restrictedResource->getSecurityRoles()) !== null &&
107
            !$this->rolesVote($roles)
108
        ) {
109
            return null;
110
        }
111
        $context[self::ALREADY_CALLED][] = $this->getId($object);
112
        dump($object);
113
        if ($object instanceof AbstractComponent || $object instanceof DynamicContent) {
114
            $context['groups'] = array_map(static function($grp) {
115
                if (strpos($grp, 'route') === 0) {
116
                    return str_replace('route', 'component', $grp);
117
                }
118
                return $grp;
119
            }, $context['groups']);
120
        }
121
        foreach ($this->supportedTransformers as $transformer) {
122
            $transformer->transform($object, $context);
123
        }
124
        return $this->normalizer->normalize($object, $format, $context);
125
    }
126
127
    public function hasCacheableSupportsMethod(): bool
128
    {
129
        return false;
130
    }
131
}
132