PropertyNormalizer::normalize()   B
last analyzed

Complexity

Conditions 9
Paths 7

Size

Total Lines 39
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 9

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 21
nc 7
nop 2
dl 0
loc 39
ccs 20
cts 20
cp 1
crap 9
rs 8.0555
c 1
b 0
f 0
1
<?php
2
3
namespace Bdf\Serializer\Normalizer;
4
5
use Bdf\Serializer\Context\DenormalizationContext;
6
use Bdf\Serializer\Context\NormalizationContext;
7
use Bdf\Serializer\Exception\UnexpectedValueException;
8
use Bdf\Serializer\Metadata\MetadataFactoryInterface;
9
use Bdf\Serializer\PropertyAccessor\Exception\AccessorException;
10
use Bdf\Serializer\Type\Type;
11
use Doctrine\Instantiator\Exception\ExceptionInterface;
12
use Doctrine\Instantiator\Instantiator;
13
use Doctrine\Instantiator\InstantiatorInterface;
14
15
/**
16
 * PropertyNormalizer
17
 *
18
 * @author  Seb
19
 *
20
 * @implements NormalizerInterface<object>
21
 */
22
class PropertyNormalizer implements NormalizerInterface
23
{
24
    /**
25
     * The metadata factory
26
     *
27
     * @var MetadataFactoryInterface
28
     */
29
    private $metadataFactory;
30
31
    /**
32
     * The object instantiator
33
     *
34
     * @var InstantiatorInterface|null
35
     */
36
    private $instantiator;
37
38
    /**
39
     * PropertyNormalizer constructor.
40
     *
41
     * @param MetadataFactoryInterface $metadataFactory
42
     * @param InstantiatorInterface|null $instantiator The instanciator provider. Should returns InstantiatorInterface.
43
     */
44 174
    public function __construct(MetadataFactoryInterface $metadataFactory, InstantiatorInterface $instantiator = null)
45
    {
46 174
        $this->metadataFactory = $metadataFactory;
47 174
        $this->instantiator = $instantiator;
48
    }
49
50
    /**
51
     * {@inheritdoc}
52
     *
53
     * @psalm-suppress PossiblyUndefinedVariable
54
     */
55 98
    public function normalize($data, NormalizationContext $context)
56
    {
57 98
        $hash = $context->assertNoCircularReference($data);
58
59 98
        $normalized = [];
60 98
        $metadata = $this->metadataFactory->getMetadata($data);
61
62
        // TODO Optimize the loop with the options
63 98
        foreach ($metadata->properties as $property) {
64 98
            $propertyContext = $context->duplicate($property->normalizationOptions);
65
66 98
            if ($propertyContext->skipProperty($property)) {
67 18
                continue;
68
            }
69
70
            try {
71 98
                $value = $propertyContext->root()->normalize($property->accessor->read($data), $propertyContext);
72 12
            } catch (AccessorException $exception) {
73 10
                if ($propertyContext->throwsOnAccessorError()) {
74 2
                    throw $exception;
75
                }
76
77 8
                continue;
78
            }
79
80 96
            if ($propertyContext->skipPropertyValue($property, $value)) {
81 12
                continue;
82
            }
83
84 94
            if ($property->inline && is_array($value) && !$context->includeMetaType()) {
85 2
                $normalized += $value;
86
            } else {
87 94
                $normalized[$property->alias] = $value;
88
            }
89
        }
90
91 94
        $context->releaseReference($hash);
92
93 94
        return $normalized;
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99 66
    public function denormalize($data, Type $type, DenormalizationContext $context)
100
    {
101 66
        $object = $this->instantiate($type);
102 66
        $metadata = $this->metadataFactory->getMetadata($object);
103
104 66
        foreach ((array)$data as $name => $propertyData) {
105 66
            if (($property = $metadata->property($name)) === null) {
106 2
                continue;
107
            }
108
109 64
            $propertyContext = $context->duplicate($property->denormalizationOptions);
110
111 64
            if ($propertyContext->skipProperty($property)) {
112 2
                continue;
113
            }
114
115
            // If type is an object we should try to inject
116
            // the new value into the object of the owner object
117 62
            if (!$property->type->isBuildin()) {
118
                try {
119 20
                    $current = $property->accessor->read($object);
120
121
                    // if current is an object we put it on the queue of targets
122 18
                    if (is_object($current)) {
123 18
                        $property->type->setTarget($current);
124
                    }
125 2
                } catch (AccessorException $exception) {
126
                    // Silent mode: if value is undefined we let the next denormalize create the object
127
                }
128
            }
129
130
            try {
131 62
                $property->accessor->write(
132 62
                    $object,
133 62
                    $propertyContext->root()->denormalize($propertyData, $property->type, $propertyContext)
134 62
                );
135 6
            } catch (AccessorException $exception) {
136 6
                if ($propertyContext->throwsOnAccessorError()) {
137 2
                    throw $exception;
138
                }
139
140 4
                continue;
141
            } finally {
142
                // memory leaks
143 62
                $property->type->setTarget(null);
144
            }
145
        }
146
147 64
        $metadata->postDenormalization($object);
148
149 64
        return $object;
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155 156
    public function supports(string $className): bool
156
    {
157 156
        return class_exists($className);
158
    }
159
160
    /**
161
     * Instanciate an object
162
     *
163
     * @param Type<object> $type
164
     *
165
     * @return object
166
     *
167
     * @throws UnexpectedValueException  If instanciate could not instanciate type
168
     */
169 66
    private function instantiate($type)
170
    {
171 66
        if ($type->target()) {
172 6
            return $type->target();
173
        }
174
175 60
        if ($this->instantiator === null) {
176 60
            $this->instantiator = new Instantiator();
177
        }
178
179
        try {
180 60
            return $this->instantiator->instantiate($type->name());
181
        } catch (ExceptionInterface $e) {
182
            throw new UnexpectedValueException("Could not instantiate object '".$type->name()."'", 0, $e);
183
        }
184
    }
185
}
186