Completed
Push — master ( c34cac...3c68a7 )
by Pieter
04:47
created

ApieObjectNormalizer::isAllowedAttribute()   B

Complexity

Conditions 9
Paths 7

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 16
c 0
b 0
f 0
nc 7
nop 4
dl 0
loc 22
rs 8.0555
1
<?php
2
namespace W2w\Lib\Apie\Plugins\Core\Normalizers;
3
4
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
5
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
6
use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
7
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
8
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
9
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
10
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
11
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
12
use Throwable;
13
use W2w\Lib\Apie\Exceptions\ValidationException;
14
15
/**
16
 * Class overriding ObjectNormalizer to workaround https://github.com/symfony/symfony/issues/33622
17
 */
18
class ApieObjectNormalizer extends ObjectNormalizer
19
{
20
    /**
21
     * @var PropertyInfoExtractor|null
22
     */
23
    private $propertyInfoExtractor;
24
25
    /**
26
     * @param ClassMetadataFactoryInterface|null $classMetadataFactory
27
     * @param NameConverterInterface|null $nameConverter
28
     * @param PropertyAccessorInterface|null $propertyAccessor
29
     * @param PropertyInfoExtractor|null $propertyInfoExtractor
30
     * @param ClassDiscriminatorResolverInterface|null $classDiscriminatorResolver
31
     * @param callable|null $objectClassResolver
32
     * @param array $defaultContext
33
     */
34
    public function __construct(
35
        ClassMetadataFactoryInterface $classMetadataFactory = null,
36
        NameConverterInterface $nameConverter = null,
37
        PropertyAccessorInterface $propertyAccessor = null,
38
        PropertyInfoExtractor $propertyInfoExtractor = null,
39
        ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null,
40
        callable $objectClassResolver = null,
41
        array $defaultContext = []
42
    ) {
43
        $this->propertyInfoExtractor = $propertyInfoExtractor;
44
        parent::__construct(
45
            $classMetadataFactory,
46
            $nameConverter,
47
            $propertyAccessor,
48
            $propertyInfoExtractor,
49
            $classDiscriminatorResolver,
50
            $objectClassResolver,
51
            $defaultContext
52
        );
53
    }
54
55
    /**
56
     * {@inheritdoc}
57
     */
58
    public function normalize($object, $format = null, array $context = [])
59
    {
60
        $context['apie_direction'] = 'read';
61
        return parent::normalize($object, $format, $context);
62
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67
    public function denormalize($data, $type, $format = null, array $context = [])
68
    {
69
        try {
70
            $context['apie_direction'] = 'write';
71
            return parent::denormalize($data, $type, $format, $context);
72
        } catch (NotNormalizableValueException $notNormalizableValueException) {
73
            $message = $notNormalizableValueException->getMessage();
74
            // Failed to denormalize attribute "%s" value for class "%s": %s.
75
            if (preg_match(
76
                '/^Failed to denormalize attribute "([\\w_]+)" value for class "([\\w\\\\]+)": (.+)\\.$/',
77
                $message,
78
                $matches
79
            )) {
80
                throw new ValidationException([$matches[1] => [$matches[3]]], $notNormalizableValueException);
81
            }
82
            // The type of the "%s" attribute for class "%s" %s.
83
            if (preg_match(
84
                '/^The type of the "([\\w_]+)" attribute for class "([\\w\\\\]+)" (.*).$/',
85
                $message,
86
                $matches
87
            )) {
88
                throw new ValidationException([$matches[1] => [$matches[3]]], $notNormalizableValueException);
89
            }
90
            throw $notNormalizableValueException;
91
        } catch (MissingConstructorArgumentsException $missingConstructorArgumentsException) {
92
            $message = $missingConstructorArgumentsException->getMessage();
93
            if (preg_match(
94
                '/^Cannot create an instance of .* from serialized data because its constructor requires parameter "([\\w_]+)" to be present.$/',
95
                $message,
96
                $matches
97
            )) {
98
                throw new ValidationException([$matches[1] => [$matches[1] . ' is required']], $missingConstructorArgumentsException);
99
            }
100
            throw $missingConstructorArgumentsException;
101
        }
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public function hasCacheableSupportsMethod(): bool
108
    {
109
        return true;
110
    }
111
112
    /**
113
     * {@inheritDoc}
114
     */
115
    protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = [])
116
    {
117
        try {
118
            parent::setAttributeValue($object, $attribute, $value, $format, $context);
119
        } catch (Throwable $throwable) {
120
            throw new ValidationException([$attribute => $throwable->getMessage()], $throwable);
121
        }
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    protected function isAllowedAttribute($classOrObject, $attribute, $format = null, array $context = [])
128
    {
129
        $isAllowed = parent::isAllowedAttribute(
130
            $classOrObject,
131
            $attribute,
132
            $format,
133
            $context
134
        );
135
        if ($isAllowed && $this->propertyInfoExtractor && !empty($context['apie_direction'])) {
136
            $className = is_object($classOrObject) ? get_class($classOrObject) : $classOrObject;
137
            switch ($context['apie_direction']) {
138
                case 'read':
139
                    return (bool) $this->propertyInfoExtractor->isReadable($className, $attribute);
140
                case 'write':
141
                    if (empty($context['object_to_populate'])) {
142
                        return $this->propertyInfoExtractor->isWritable($className, $attribute)
143
                            || $this->propertyInfoExtractor->isInitializable($className, $attribute);
144
                    }
145
                    return (bool) $this->propertyInfoExtractor->isWritable($className, $attribute);
146
            }
147
        }
148
        return $isAllowed;
149
    }
150
}
151