Completed
Pull Request — master (#60)
by
unknown
01:35
created

ObjectNormalizer::denormalizeValue()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 3
nc 3
nop 4
1
<?php
2
3
/*
4
 * (c) Kévin Dunglas <[email protected]>
5
 *
6
 * This source file is subject to the MIT license that is bundled
7
 * with this source code in the file LICENSE.
8
 */
9
10
namespace Dunglas\DoctrineJsonOdm\Normalizer;
11
12
use Doctrine\Common\Util\ClassUtils;
13
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
14
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
15
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
16
use Symfony\Component\Serializer\SerializerAwareInterface;
17
use Symfony\Component\Serializer\SerializerInterface;
18
19
/**
20
 * Transforms an object to an array.
21
 *
22
 * @author Kévin Dunglas <[email protected]>
23
 */
24
final class ObjectNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface
25
{
26
    // To not use the current normalizer
27
    const WONT_DENORMALIZE = 'dunglas_normalizer_wont_denormalize';
28
    const TYPE_FIELD = '#type';
29
30
    /**
31
     * @var NormalizerInterface|DenormalizerInterface
32
     */
33
    private $serializer;
34
    private $objectNormalizer;
35
36
    public function __construct(NormalizerInterface $objectNormalizer)
37
    {
38
        if (!$objectNormalizer instanceof DenormalizerInterface) {
39
            throw new \InvalidArgumentException(sprintf('The normalizer used must implement the "%s" interface.', DenormalizerInterface::class));
40
        }
41
42
        $this->objectNormalizer = $objectNormalizer;
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function normalize($object, $format = null, array $context = [])
49
    {
50
        return \array_merge([self::TYPE_FIELD => ClassUtils::getClass($object)], $this->objectNormalizer->normalize($object, $format, $context));
51
    }
52
53
    /**
54
     * {@inheritdoc}
55
     */
56
    public function supportsNormalization($data, $format = null)
57
    {
58
        return \is_object($data);
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64
    public function denormalize($data, $class, $format = null, array $context = [])
65
    {
66
        if (!\is_iterable($data) || isset($context[self::WONT_DENORMALIZE]) || \is_object($data)) {
67
            return $data;
68
        }
69
70
        if (null !== $type = $this->extractType($data)) {
71
            return $this->denormalizeObject($data, $type, $format, $context);
0 ignored issues
show
Bug introduced by
It seems like $format defined by parameter $format on line 64 can also be of type string; however, Dunglas\DoctrineJsonOdm\...er::denormalizeObject() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
72
        }
73
74
        foreach ($data as $key => $value) {
75
            $data[$key] = $this->denormalizeValue($value, $class, $format, $context);
0 ignored issues
show
Bug introduced by
It seems like $format defined by parameter $format on line 64 can also be of type string; however, Dunglas\DoctrineJsonOdm\...zer::denormalizeValue() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
76
        }
77
78
        return $data;
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84
    public function supportsDenormalization($data, $type, $format = null)
85
    {
86
        return true;
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92
    public function setSerializer(SerializerInterface $serializer)
93
    {
94
        if (!$serializer instanceof NormalizerInterface || !$serializer instanceof DenormalizerInterface) {
95
            throw new \InvalidArgumentException(
96
                sprintf('The injected serializer must implement "%s" and "%s".', NormalizerInterface::class, DenormalizerInterface::class)
97
            );
98
        }
99
100
        $this->serializer = $serializer;
101
102
        if ($this->objectNormalizer instanceof SerializerAwareInterface) {
103
            $this->objectNormalizer->setSerializer($serializer);
104
        }
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110
    public function hasCacheableSupportsMethod(): bool
111
    {
112
        return true;
113
    }
114
115
    /**
116
     * Converts data to $class' object if possible.
117
     *
118
     * @param array  $data
119
     * @param string $class
120
     * @param null   $format
121
     * @param array  $context
122
     *
123
     * @return object|null
124
     */
125
    private function denormalizeObject(array $data, string $class, $format = null, array $context = [])
126
    {
127
    	// Try to denormalize in other normalizers. Necessary to avoid cycle
128
	    $context[self::WONT_DENORMALIZE] = true;
129
	    if (\is_object($denormalizedValue = $this->serializer->denormalize($data, $class, $format, $context))) {
0 ignored issues
show
Bug introduced by
The method denormalize does only exist in Symfony\Component\Serial...r\DenormalizerInterface, but not in Symfony\Component\Serial...zer\NormalizerInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
130
	    	return $denormalizedValue;
131
	    }
132
133
	    // Denormalize in current default normalizer
134
	    unset($context[self::WONT_DENORMALIZE]);
135
136
	    foreach ($data as $key => $value) {
137
		    $data[$key] = $this->denormalizeValue($value, $class, $format, $context);
138
	    }
139
140
	    return $this->objectNormalizer->denormalize($data, $class, $format, $context);
141
    }
142
143
    /**
144
     * Convert raw value to normalized value - object or primitive type.
145
     *
146
     * @param mixed  $value
147
     * @param string $class
148
     * @param null   $format
149
     * @param array  $context
150
     *
151
     * @return object|null
152
     */
153
    private function denormalizeValue($value, string $class, $format = null, array $context = [])
154
    {
155
        if (\is_object($value)) {
156
            return $value;
157
        }
158
159
        return (null !== $type = $this->extractType($value))
160
            ? $this->denormalizeObject($value, $type, $format, $context)
161
            : $this->serializer->denormalize($value, $class, $format, $context);
0 ignored issues
show
Bug introduced by
The method denormalize does only exist in Symfony\Component\Serial...r\DenormalizerInterface, but not in Symfony\Component\Serial...zer\NormalizerInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
162
    }
163
164
    /**
165
     * Grab class from array.
166
     *
167
     * @param $data
168
     *
169
     * @return string|null
170
     */
171
    private function extractType(&$data)
172
    {
173
        if (!isset($data[self::TYPE_FIELD])) {
174
            return null;
175
        }
176
177
        $type = $data[self::TYPE_FIELD];
178
        unset($data[self::TYPE_FIELD]);
179
180
        return $type;
181
    }
182
}
183