Completed
Pull Request — master (#812)
by
unknown
03:54
created

DoctrineObjectConstructor::getPropertyGroups()   C

Complexity

Conditions 8
Paths 12

Size

Total Lines 35
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 18.3887

Importance

Changes 0
Metric Value
dl 0
loc 35
ccs 10
cts 22
cp 0.4545
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 20
nc 12
nop 3
crap 18.3887
1
<?php
2
3
/*
4
 * Copyright 2016 Johannes M. Schmitt <[email protected]>
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace JMS\Serializer\Construction;
20
21
use Doctrine\Common\Persistence\ManagerRegistry;
22
use JMS\Serializer\Context;
23
use JMS\Serializer\DeserializationContext;
24
use JMS\Serializer\Exception\InvalidArgumentException;
25
use JMS\Serializer\Exception\ObjectConstructionException;
26
use JMS\Serializer\Metadata\ClassMetadata;
27
use JMS\Serializer\VisitorInterface;
28
use Metadata\ClassHierarchyMetadata;
29
use Metadata\Driver\DriverInterface;
30
use PhpOption\None;
31
use JMS\Serializer\Metadata\Driver\AnnotationDriver;
32
use JMS\Serializer\Metadata\ClassMetadata as JMSClassMetadata;
33
use \Doctrine\Common\Persistence\Mapping\ClassMetadata as DoctrineClassMetadata;
34
35
/**
36
 * Doctrine object constructor for new (or existing) objects during deserialization.
37
 */
38
class DoctrineObjectConstructor implements ObjectConstructorInterface
39
{
40
    const ON_MISSING_NULL = 'null';
41
    const ON_MISSING_EXCEPTION = 'exception';
42
    const ON_MISSING_FALLBACK = 'fallback';
43
    /**
44
     * @var string
45
     */
46
    private $fallbackStrategy;
47
48
    private $managerRegistry;
49
    private $fallbackConstructor;
50
51
    /** @var AnnotationDriver */
52
    private $annotationDriver;
0 ignored issues
show
Unused Code introduced by
The property $annotationDriver is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
53
54
    /**
55
     * Constructor.
56
     *
57
     * @param ManagerRegistry $managerRegistry Manager registry
58
     * @param ObjectConstructorInterface $fallbackConstructor Fallback object constructor
59
     * @param string $fallbackStrategy
60
     */
61 11
    public function __construct(ManagerRegistry $managerRegistry, ObjectConstructorInterface $fallbackConstructor, $fallbackStrategy = self::ON_MISSING_NULL)
62
    {
63 11
        $this->managerRegistry = $managerRegistry;
64 11
        $this->fallbackConstructor = $fallbackConstructor;
65 11
        $this->fallbackStrategy = $fallbackStrategy;
66 11
    }
67
68 2
    protected function getPropertyGroups(Context $context, $className, $property)
69
    {
70 2
        $classMetadataHierarchy = $context->getMetadataFactory()->getMetadataForClass($className);
71
72 2
        if (true === $classMetadataHierarchy instanceof ClassHierarchyMetadata) {
73
            $isClassMetadataHierarchy = true;
74
            $classMetadata = current($classMetadataHierarchy->classMetadata);
75
        } else {
76 2
            $classMetadata = $classMetadataHierarchy;
77 2
            $isClassMetadataHierarchy = false;
78
        }
79
80
        // up to the hierarchy
81 2
        while (null !== $classMetadata && false !== $classMetadata) {
82
            // limit case
83 2
            if (array_key_exists($property, $classMetadata->propertyMetadata)) {
84 2
                $propertyGroups = $classMetadata->propertyMetadata[$property]->groups;
85
86 2
                if (null === $propertyGroups) {
87
                    $propertyGroups = array();
88
                }
89
90 2
                return $propertyGroups;
91
            }
92
93
            if (true === $isClassMetadataHierarchy) {
94
                $classMetadata = next($classMetadataHierarchy->classMetadata);
0 ignored issues
show
Bug introduced by
The property classMetadata does not seem to exist in Metadata\ClassMetadata.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
95
            } else {
96
                $parent = $classMetadata->reflection->getParentClass();
97
                $classMetadata = ($parent)? $context->getMetadataFactory()->getMetadataForClass($parent->getName()) : null;
98
            }
99
        }
100
101
        return array();
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107 11
    public function construct(VisitorInterface $visitor, ClassMetadata $metadata, $data, array $type, DeserializationContext $context)
108
    {
109
        // Locate possible ObjectManager
110 11
        $objectManager = $this->managerRegistry->getManagerForClass($metadata->name);
111
112 11
        if (!$objectManager) {
113
            // No ObjectManager found, proceed with normal deserialization
114
            return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
115
        }
116
117
        // Locate possible ClassMetadata
118 11
        $classMetadataFactory = $objectManager->getMetadataFactory();
119
120 11
        if ($classMetadataFactory->isTransient($metadata->name)) {
121
            // No ClassMetadata found, proceed with normal deserialization
122
            return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
123
        }
124
125
        // Managed entity, check for proxy load
126 11
        if (!is_array($data)) {
127
            // Single identifier, load proxy
128 1
            return $objectManager->getReference($metadata->name, $data);
129
        }
130
131
        // Fallback to default constructor if missing identifier(s)
132
        /** @var DoctrineClassMetadata $classMetadata*/
133 10
        $classMetadata  = $objectManager->getClassMetadata($metadata->name);
134 10
        $identifierList = array();
135
        /** @var array $deserializingGroups */
136 10
        $deserializingGroups = $context->getGroups()->getOrElse(array());
137
138
        // Avoid calling objectManager->find if the deserialization context groups exclude identification properties
139 10
        foreach ($classMetadata->getIdentifierFieldNames() as $name) {
140 10
            $missingIdentifier = !array_key_exists($name, $data);
141
142 10
            if (0 < count($deserializingGroups) && false === $missingIdentifier) {
143 2
                $propertyGroups = $this->getPropertyGroups($context, $metadata->name, $name);
144 2
                $groupForIdentFound = false;
145
146
                // group list match on at least one group?
147 2
                foreach ($deserializingGroups as $deserializingGroup) {
148 2
                    if (in_array($deserializingGroup, $propertyGroups)) {
149 1
                        $groupForIdentFound = true;
150 1
                        break;
151
                    }
152 2
                }
153
154 2
                $missingIdentifier = $missingIdentifier || (!$groupForIdentFound);
155 2
            }
156
157 10
            if (true === $missingIdentifier) {
158 2
                return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
159
            }
160
161 8
            $identifierList[$name] = $data[$name];
162 8
        }
163
164
        // Entity update, load it from database
165 8
        $object = $objectManager->find($metadata->name, $identifierList);
166
167 8
        if (null === $object) {
168 5
            switch ($this->fallbackStrategy) {
169 5
                case self::ON_MISSING_NULL:
170 1
                    return null;
171 4
                case self::ON_MISSING_EXCEPTION:
172 1
                    throw new ObjectConstructionException(sprintf("Entity %s can not be found", $metadata->name));
173 3
                case self::ON_MISSING_FALLBACK:
174 2
                    return $this->fallbackConstructor->construct($visitor, $metadata, $data, $type, $context);
175 1
                default:
176 1
                    throw new InvalidArgumentException("The provided fallback strategy for the object constructor is not valid");
177 1
            }
178
        }
179
180 3
        $objectManager->initializeObject($object);
181
182 3
        return $object;
183
    }
184
}
185