Completed
Push — master ( 080f2d...aab704 )
by Daniel
13:31
created

ApiNormalizer::normalize()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 10
Bugs 1 Features 1
Metric Value
eloc 15
c 10
b 1
f 1
dl 0
loc 22
ccs 0
cts 0
cp 0
rs 8.8333
cc 7
nc 5
nop 3
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\RestrictedResourceInterface;
11
use Symfony\Component\Security\Core\Security;
12
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
13
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
14
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
15
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
16
17
class ApiNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface, CacheableSupportsMethodInterface
18
{
19
    use NormalizerAwareTrait;
20
21
    private const ALREADY_CALLED = 'API_COMPONENT_BUNDLE_NORMALIZER_ALREADY_CALLED';
22
23
    /** @var iterable|DataTransformerInterface[] */
24
    private $dataTransformers;
25
26
    /** @var DataTransformerInterface[] */
27
    private $supportedTransformers = [];
28
29
    private $security;
30
31
    public function __construct(iterable $dataTransformers = [], Security $security)
32
    {
33
        $this->dataTransformers = $dataTransformers;
34
        $this->security = $security;
35
    }
36
37
    private function isRestrictedResource($data): ?RestrictedResourceInterface
38
    {
39
        if ($data instanceof RestrictedResourceInterface && !$data instanceof AbstractContent) {
40
            return $data;
41
        }
42
        return null;
43
    }
44
45
    public function supportsNormalization($data, $format = null, array $context = []): bool
46
    {
47
        if (!isset($context[self::ALREADY_CALLED])) {
48
            $context[self::ALREADY_CALLED] = [];
49
        }
50
        $this->supportedTransformers = [];
51
52
        if (!is_object($data) || in_array($data, $context[self::ALREADY_CALLED], true)) {
53
            return false;
54
        }
55
56
        if ($data instanceof AbstractComponent) {
57
            return true;
58
        }
59
60
        if ($this->isRestrictedResource($data)) {
61
            return true;
62
        }
63
64
        foreach ($this->dataTransformers as $transformer) {
65
            if ($transformer->supportsTransformation($data)) {
66
                $this->supportedTransformers[] = $transformer;
67
            }
68
        }
69
        return !empty($this->supportedTransformers);
70
    }
71
72
    private function rolesVote(iterable $roles): bool
73
    {
74
        $negativeRoles = [];
75
        $positiveRoles = [];
76
        foreach ($roles as $role) {
77
            if (strpos($role, '!') === 0) {
78
                $negativeRoles[] = substr($role, 1);
79
                continue;
80
            }
81
            $positiveRoles[] = $role;
82
        }
83
        $positivePass = count($positiveRoles) && $this->security->isGranted($positiveRoles);
84
        $negativePass = count($negativeRoles) && !$this->security->isGranted($negativeRoles);
85
        return $positivePass || $negativePass;
86
    }
87
88
    public function normalize($object, $format = null, array $context = [])
89
    {
90
        if (
91
            ($restrictedResource = $this->isRestrictedResource($object)) &&
92
            ($roles = $restrictedResource->getSecurityRoles()) !== null &&
93
            !$this->rolesVote($roles)
94
        ) {
95
            return null;
96
        }
97
        $context[self::ALREADY_CALLED][] = $object;
98
        if ($object instanceof AbstractComponent) {
99
            $context['groups'] = array_map(static function($grp) {
100
                if (strpos($grp, 'route') === 0) {
101
                    return str_replace('route', 'component', $grp);
102
                }
103
                return $grp;
104
            }, $context['groups']);
105
        }
106
        foreach ($this->supportedTransformers as $transformer) {
107
            $transformer->transform($object, $context);
108
        }
109
        return $this->normalizer->normalize($object, $format, $context);
110
    }
111
112
    public function hasCacheableSupportsMethod(): bool
113
    {
114
        return false;
115
    }
116
}
117