Passed
Push — master ( f22622...3010bf )
by Daniel
06:37
created

ApiNormalizer::rolesVote()   B

Complexity

Conditions 7
Paths 25

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 12
c 3
b 0
f 0
dl 0
loc 17
ccs 0
cts 0
cp 0
rs 8.8333
nc 25
nop 1
cc 7
crap 56
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(array $roles): bool
87
    {
88
        if (!count($roles)) {
89
            return true;
90
        }
91
        $negativeRoles = [];
92
        $positiveRoles = [];
93
        foreach ($roles as $role) {
94
            if (strpos($role, '!') === 0) {
95
                $negativeRoles[] = substr($role, 1);
96
                continue;
97
            }
98
            $positiveRoles[] = $role;
99
        }
100
        $positivePass = count($positiveRoles) && $this->security->isGranted($positiveRoles);
101
        $negativePass = count($negativeRoles) && !$this->security->isGranted($negativeRoles);
102
        return $positivePass || $negativePass;
103
    }
104
105
    public function normalize($object, $format = null, array $context = [])
106
    {
107
        if (
108
            ($restrictedResource = $this->isRestrictedResource($object)) &&
109
            ($roles = $restrictedResource->getSecurityRoles()) !== null &&
110
            !$this->rolesVote($roles)
111
        ) {
112
            return null;
113
        }
114
        $context[self::ALREADY_CALLED][] = $this->getId($object);
115
        if ($object instanceof AbstractComponent || $object instanceof DynamicContent) {
116
            $context['groups'] = array_map(static function($grp) {
117
                if (strpos($grp, 'route') === 0) {
118
                    return str_replace('route', 'component', $grp);
119
                }
120
                return $grp;
121
            }, $context['groups']);
122
        }
123
        foreach ($this->supportedTransformers as $transformer) {
124
            $transformer->transform($object, $context);
125
        }
126
        return $this->normalizer->normalize($object, $format, $context);
127
    }
128
129
    public function hasCacheableSupportsMethod(): bool
130
    {
131
        return false;
132
    }
133
}
134