Completed
Pull Request — master (#1246)
by Asmir
14:15
created

isIdentifierFieldExcluded()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 4
nc 3
nop 2
dl 0
loc 8
ccs 0
cts 0
cp 0
crap 20
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace JMS\Serializer\Construction;
6
7
use Doctrine\Persistence\ManagerRegistry;
8
use JMS\Serializer\DeserializationContext;
9
use JMS\Serializer\Exception\InvalidArgumentException;
10
use JMS\Serializer\Exception\ObjectConstructionException;
11
use JMS\Serializer\Exclusion\ExpressionLanguageExclusionStrategy;
12
use JMS\Serializer\Metadata\ClassMetadata;
13
use JMS\Serializer\Metadata\PropertyMetadata;
14
use JMS\Serializer\Visitor\DeserializationVisitorInterface;
15
16
/**
17
 * Doctrine object constructor for new (or existing) objects during deserialization.
18
 */
19
final class DoctrineObjectConstructor implements ObjectConstructorInterface
20
{
21
    public const ON_MISSING_NULL = 'null';
22
    public const ON_MISSING_EXCEPTION = 'exception';
23
    public const ON_MISSING_FALLBACK = 'fallback';
24
    /**
25
     * @var string
26
     */
27
    private $fallbackStrategy;
28
29
    /**
30
     * @var ManagerRegistry
31
     */
32
    private $managerRegistry;
33
34
    /**
35
     * @var ObjectConstructorInterface
36
     */
37 10
    private $fallbackConstructor;
38
39 10
    /**
40 10
     * @var ExpressionLanguageExclusionStrategy|null
41 10
     */
42 10
    private $expressionLanguageExclusionStrategy;
43
44
    /**
45
     * @param ManagerRegistry $managerRegistry     Manager registry
46
     * @param ObjectConstructorInterface $fallbackConstructor Fallback object constructor
47 10
     */
48
    public function __construct(
49
        ManagerRegistry $managerRegistry,
50 10
        ObjectConstructorInterface $fallbackConstructor,
51
        string $fallbackStrategy = self::ON_MISSING_NULL,
52 10
        ?ExpressionLanguageExclusionStrategy $expressionLanguageExclusionStrategy = null
53
    ) {
54
        $this->managerRegistry = $managerRegistry;
55
        $this->fallbackConstructor = $fallbackConstructor;
56
        $this->fallbackStrategy = $fallbackStrategy;
57
        $this->expressionLanguageExclusionStrategy = $expressionLanguageExclusionStrategy;
58 10
    }
59
60 10
    /**
61
     * {@inheritdoc}
62
     */
63
    public function construct(DeserializationVisitorInterface $visitor, ClassMetadata $metadata, $data, array $type, DeserializationContext $context): ?object
64
    {
65
        // Locate possible ObjectManager
66 10
        $objectManager = $this->managerRegistry->getManagerForClass($metadata->name);
67
68 1
        if (!$objectManager) {
69
            // No ObjectManager found, proceed with normal deserialization
70
            return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
71
        }
72 9
73 9
        // Locate possible ClassMetadata
74
        $classMetadataFactory = $objectManager->getMetadataFactory();
75 9
76
        if ($classMetadataFactory->isTransient($metadata->name)) {
77 9
            // No ClassMetadata found, proceed with normal deserialization
78 1
            return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
79
        }
80 8
81
        // Managed entity, check for proxy load
82
        if (!\is_array($data) && !(is_object($data) && 'SimpleXMLElement' === get_class($data))) {
83 9
            // Single identifier, load proxy
84 1
            return $objectManager->getReference($metadata->name, $data);
0 ignored issues
show
Bug introduced by
The method getReference() does not exist on Doctrine\Persistence\ObjectManager. It seems like you code against a sub-type of said class. However, the method does not exist in Doctrine\Persistence\ObjectManagerDecorator or Doctrine\Common\Persistence\ObjectManager or Doctrine\Common\Persistence\ObjectManagerDecorator. Are you sure you never get one of those? ( Ignorable by Annotation )

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

84
            return $objectManager->/** @scrutinizer ignore-call */ getReference($metadata->name, $data);
Loading history...
85
        }
86 8
87
        // Fallback to default constructor if missing identifier(s)
88
        $classMetadata = $objectManager->getClassMetadata($metadata->name);
89
        $identifierList = [];
90 8
91
        foreach ($classMetadata->getIdentifierFieldNames() as $name) {
92 8
            if (isset($metadata->propertyMetadata[$name])) {
93 5
                $propertyMetadata = $metadata->propertyMetadata[$name];
94 5
95 1
                // Avoid calling objectManager->find if some identification properties are excluded
96 4
                if ($this->isIdentifierFieldExcluded($propertyMetadata, $context)) {
97 1
                    return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
98 3
                }
99 2
100
                $dataName = $propertyMetadata->serializedName;
101 1
            } else {
102
                $dataName = $name;
103
            }
104
105 3
            if (!array_key_exists($dataName, $data)) {
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type object; however, parameter $search of array_key_exists() does only seem to accept array, 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

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