Passed
Pull Request — master (#5)
by Sébastien
10:01
created

PropertyNormalizer::instantiate()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.25

Importance

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