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

DoctrineObjectConstructor::construct()   C

Complexity

Conditions 15
Paths 50

Size

Total Lines 77
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 15.0242

Importance

Changes 0
Metric Value
dl 0
loc 77
ccs 40
cts 42
cp 0.9524
rs 5.2029
c 0
b 0
f 0
cc 15
eloc 38
nc 50
nop 5
crap 15.0242

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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