Issues (163)

src/Construction/DoctrineObjectConstructor.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace JMS\Serializer\Construction;
6
7
use Doctrine\ODM\PHPCR\DocumentManagerInterface;
8
use Doctrine\ORM\EntityManagerInterface;
9
use Doctrine\Persistence\ManagerRegistry;
10
use JMS\Serializer\DeserializationContext;
11
use JMS\Serializer\Exception\InvalidArgumentException;
12
use JMS\Serializer\Exception\ObjectConstructionException;
13
use JMS\Serializer\Exclusion\ExpressionLanguageExclusionStrategy;
14
use JMS\Serializer\Metadata\ClassMetadata;
15
use JMS\Serializer\Metadata\PropertyMetadata;
16
use JMS\Serializer\Visitor\DeserializationVisitorInterface;
17
18
use function is_array;
19
20
/**
21
 * Doctrine object constructor for new (or existing) objects during deserialization.
22
 */
23
final class DoctrineObjectConstructor implements ObjectConstructorInterface
24
{
25
    public const ON_MISSING_NULL = 'null';
26
    public const ON_MISSING_EXCEPTION = 'exception';
27
    public const ON_MISSING_FALLBACK = 'fallback';
28
    /**
29
     * @var string
30
     */
31
    private $fallbackStrategy;
32
33
    /**
34
     * @var ManagerRegistry
35
     */
36
    private $managerRegistry;
37 10
38
    /**
39 10
     * @var ObjectConstructorInterface
40 10
     */
41 10
    private $fallbackConstructor;
42 10
43
    /**
44
     * @var ExpressionLanguageExclusionStrategy|null
45
     */
46
    private $expressionLanguageExclusionStrategy;
47 10
48
    /**
49
     * @param ManagerRegistry $managerRegistry     Manager registry
50 10
     * @param ObjectConstructorInterface $fallbackConstructor Fallback object constructor
51
     */
52 10
    public function __construct(
53
        ManagerRegistry $managerRegistry,
54
        ObjectConstructorInterface $fallbackConstructor,
55
        string $fallbackStrategy = self::ON_MISSING_NULL,
56
        ?ExpressionLanguageExclusionStrategy $expressionLanguageExclusionStrategy = null
57
    ) {
58 10
        $this->managerRegistry = $managerRegistry;
59
        $this->fallbackConstructor = $fallbackConstructor;
60 10
        $this->fallbackStrategy = $fallbackStrategy;
61
        $this->expressionLanguageExclusionStrategy = $expressionLanguageExclusionStrategy;
62
    }
63
64
    /**
65
     * {@inheritdoc}
66 10
     */
67
    public function construct(DeserializationVisitorInterface $visitor, ClassMetadata $metadata, $data, array $type, DeserializationContext $context): ?object
68 1
    {
69
        $objectManager = $this->managerRegistry->getManagerForClass($metadata->name);
70
71
        if (!$objectManager) {
72 9
            // No ObjectManager found, proceed with normal deserialization
73 9
            return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
74
        }
75 9
76
        // Locate possible ClassMetadata
77 9
        $classMetadataFactory = $objectManager->getMetadataFactory();
78 1
79
        if ($classMetadataFactory->isTransient($metadata->name)) {
80 8
            // No ClassMetadata found, proceed with normal deserialization
81
            return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
82
        }
83 9
84 1
        // Managed entity, check for proxy load
85
        if (!is_array($data) && !(is_object($data) && 'SimpleXMLElement' === get_class($data))) {
86 8
            \assert($objectManager instanceof EntityManagerInterface || $objectManager instanceof DocumentManagerInterface);
87
88
            // Single identifier, load proxy
89
            return $objectManager->getReference($metadata->name, $data);
90 8
        }
91
92 8
        // Fallback to default constructor if missing identifier(s)
93 5
        $classMetadata = $objectManager->getClassMetadata($metadata->name);
94 5
        $identifierList = [];
95 1
96 4
        foreach ($classMetadata->getIdentifierFieldNames() as $name) {
97 1
            // Avoid calling objectManager->find if some identification properties are excluded
98 3
            if (!isset($metadata->propertyMetadata[$name])) {
99 2
                return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
100
            }
101 1
102
            $propertyMetadata = $metadata->propertyMetadata[$name];
103
104
            // Avoid calling objectManager->find if some identification properties are excluded by some exclusion strategy
105 3
            if ($this->isIdentifierFieldExcluded($propertyMetadata, $context)) {
106
                return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
107 3
            }
108
109
            if (is_array($data) && !array_key_exists($propertyMetadata->serializedName, $data)) {
110
                return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
111
            } elseif (is_object($data) && !property_exists($data, $propertyMetadata->serializedName)) {
0 ignored issues
show
It seems like $propertyMetadata->serializedName can also be of type null; however, parameter $property of property_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

111
            } elseif (is_object($data) && !property_exists($data, /** @scrutinizer ignore-type */ $propertyMetadata->serializedName)) {
Loading history...
112
                return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
113
            }
114
115
            if (is_object($data) && 'SimpleXMLElement' === get_class($data)) {
116
                $identifierList[$name] = (string) $data->{$propertyMetadata->serializedName};
117
            } else {
118
                $identifierList[$name] = $data[$propertyMetadata->serializedName];
119
            }
120
        }
121
122
        if (empty($identifierList)) {
123
            // $classMetadataFactory->isTransient() fails on embeddable class with file metadata driver
124
            // https://github.com/doctrine/persistence/issues/37
125
            return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
126
        }
127
128
        // Entity update, load it from database
129
        $object = $objectManager->find($metadata->name, $identifierList);
130
131
        if (null === $object) {
132
            switch ($this->fallbackStrategy) {
133
                case self::ON_MISSING_NULL:
134
                    return null;
135
136
                case self::ON_MISSING_EXCEPTION:
137
                    throw new ObjectConstructionException(sprintf('Entity %s can not be found', $metadata->name));
138
139
                case self::ON_MISSING_FALLBACK:
140
                    return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
141
142
                default:
143
                    throw new InvalidArgumentException('The provided fallback strategy for the object constructor is not valid');
144
            }
145
        }
146
147
        $objectManager->initializeObject($object);
148
149
        return $object;
150
    }
151
152
    private function isIdentifierFieldExcluded(PropertyMetadata $propertyMetadata, DeserializationContext $context): bool
153
    {
154
        $exclusionStrategy = $context->getExclusionStrategy();
155
        if (null !== $exclusionStrategy && $exclusionStrategy->shouldSkipProperty($propertyMetadata, $context)) {
156
            return true;
157
        }
158
159
        return null !== $this->expressionLanguageExclusionStrategy && $this->expressionLanguageExclusionStrategy->shouldSkipProperty($propertyMetadata, $context);
160
    }
161
}
162