Completed
Pull Request — master (#4)
by
unknown
02:48
created

ArrayHydrator   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 183
Duplicated Lines 3.28 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 4
dl 6
loc 183
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A hydrate() 0 13 4
A setHydrateAssociationReferences() 0 4 1
B hydrateProperties() 0 27 5
B hydrateAssociations() 6 17 5
A hydrateToOneAssociation() 0 11 2
B hydrateToManyAssociation() 0 19 5
A setProperty() 0 8 2
A fetchAssociationEntity() 0 8 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
namespace pmill\Doctrine\Hydrator;
3
4
use Doctrine\DBAL\Platforms\AbstractPlatform;
5
use Doctrine\DBAL\Types\Type;
6
use Doctrine\ORM\EntityManager;
7
use Doctrine\ORM\Mapping\ClassMetadataInfo;
8
use Exception;
9
10
class ArrayHydrator
11
{
12
    /**
13
     * @var EntityManager
14
     */
15
    protected $entityManager;
16
17
    /**
18
     * @var bool
19
     */
20
    protected $hydrateAssociationReferences = true;
21
22
    /**
23
     * @param EntityManager $entityManager
24
     */
25
    public function __construct(EntityManager $entityManager)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $entityManager. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
26
    {
27
        $this->entityManager = $entityManager;
28
    }
29
30
    /**
31
     * @param $entity
32
     * @param array $data
33
     * @return mixed|object
34
     * @throws Exception
35
     */
36
    public function hydrate($entity, array $data)
37
    {
38
        if (is_string($entity) && class_exists($entity)) {
39
            $entity = new $entity;
40
        }
41
        elseif (!is_object($entity)) {
42
            throw new Exception('Entity passed to ArrayHydrator::hydrate() must be a class name or entity object');
43
        }
44
45
        $entity = $this->hydrateProperties($entity, $data);
46
        $entity = $this->hydrateAssociations($entity, $data);
47
        return $entity;
48
    }
49
50
    /**
51
     * @param boolean $hydrateAssociationReferences
52
     */
53
    public function setHydrateAssociationReferences($hydrateAssociationReferences)
54
    {
55
        $this->hydrateAssociationReferences = $hydrateAssociationReferences;
56
    }
57
58
    /**
59
     * @param $entity
60
     * @param $data
61
     * @return object
62
     */
63
    protected function hydrateProperties($entity, $data)
64
    {
65
        $reflectionObject = new \ReflectionObject($entity);
66
67
        $metaData = $this->entityManager->getClassMetadata(get_class($entity));
68
        
69
        $platform = $this->entityManager->getConnection()
70
                                        ->getDatabasePlatform();
71
        
72
        foreach ($metaData->columnNames as $propertyName) {
73
            if (isset($data[$propertyName]) && !in_array($propertyName, $metaData->identifier)) {
74
                $value = $data[$propertyName];
75
                
76
                if (array_key_exists('type', $metaData->fieldMappings[$propertyName])) {
77
                    $fieldType = $metaData->fieldMappings[$propertyName]['type'];
78
                    
79
                    $type = Type::getType($fieldType);
80
                    
81
                    $value = $type->convertToPHPValue($value, $platform);
82
                }
83
84
                $entity = $this->setProperty($entity, $propertyName, $value, $reflectionObject);
85
            }
86
        }
87
88
        return $entity;
89
    }
90
91
    /**
92
     * @param $entity
93
     * @param $data
94
     * @return mixed
95
     */
96
    protected function hydrateAssociations($entity, $data)
97
    {
98
        $metaData = $this->entityManager->getClassMetadata(get_class($entity));
99
        foreach ($metaData->associationMappings as $propertyName => $mapping) {
100
            if (isset($data[$propertyName])) {
101 View Code Duplication
                if (in_array($mapping['type'], [ClassMetadataInfo::ONE_TO_ONE, ClassMetadataInfo::MANY_TO_ONE])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
102
                    $entity = $this->hydrateToOneAssociation($entity, $propertyName, $mapping, $data[$propertyName]);
103
                }
104
105 View Code Duplication
                if (in_array($mapping['type'], [ClassMetadataInfo::ONE_TO_MANY, ClassMetadataInfo::MANY_TO_MANY])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
106
                    $entity = $this->hydrateToManyAssociation($entity, $propertyName, $mapping, $data[$propertyName]);
107
                }
108
            }
109
        }
110
111
        return $entity;
112
    }
113
114
    /**
115
     * @param $entity
116
     * @param $propertyName
117
     * @param $mapping
118
     * @param $value
119
     * @return mixed
120
     */
121
    protected function hydrateToOneAssociation($entity, $propertyName, $mapping, $value)
122
    {
123
        $reflectionObject = new \ReflectionObject($entity);
124
125
        $toOneAssociationObject = $this->fetchAssociationEntity($mapping['targetEntity'], $value);
126
        if (!is_null($toOneAssociationObject)) {
127
            $entity = $this->setProperty($entity, $propertyName, $toOneAssociationObject, $reflectionObject);
128
        }
129
130
        return $entity;
131
    }
132
133
    /**
134
     * @param $entity
135
     * @param $propertyName
136
     * @param $mapping
137
     * @param $value
138
     * @return mixed
139
     */
140
    protected function hydrateToManyAssociation($entity, $propertyName, $mapping, $value)
141
    {
142
        $reflectionObject = new \ReflectionObject($entity);
143
        $values = is_array($value) ? $value : [$value];
144
145
        $assocationObjects = [];
146
        foreach ($values as $value) {
147
            if (is_array($value)) {
148
                $assocationObjects[] = $this->hydrate($mapping['targetEntity'], $value);
149
            }
150
            elseif ($associationObject = $this->fetchAssociationEntity($mapping['targetEntity'], $value)) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $associationObject is correct as $this->fetchAssociationE...targetEntity'], $value) (which targets pmill\Doctrine\Hydrator\...etchAssociationEntity()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
151
                $assocationObjects[] = $associationObject;
152
            }
153
        }
154
155
        $entity = $this->setProperty($entity, $propertyName, $assocationObjects, $reflectionObject);
156
157
        return $entity;
158
    }
159
160
    /**
161
     * @param $entity
162
     * @param $propertyName
163
     * @param $value
164
     * @param null $reflectionObject
165
     * @return mixed
166
     */
167
    protected function setProperty($entity, $propertyName, $value, $reflectionObject = null)
168
    {
169
        $reflectionObject = is_null($reflectionObject) ? new \ReflectionObject($entity) : $reflectionObject;
170
        $property = $reflectionObject->getProperty($propertyName);
171
        $property->setAccessible(true);
172
        $property->setValue($entity, $value);
173
        return $entity;
174
    }
175
176
    /**
177
     * @param $className
178
     * @param $id
179
     * @return bool|\Doctrine\Common\Proxy\Proxy|null|object
180
     * @throws \Doctrine\ORM\ORMException
181
     * @throws \Doctrine\ORM\OptimisticLockException
182
     * @throws \Doctrine\ORM\TransactionRequiredException
183
     */
184
    protected function fetchAssociationEntity($className, $id)
185
    {
186
        if ($this->hydrateAssociationReferences) {
187
            return $this->entityManager->getReference($className, $id);
188
        }
189
190
        return $this->entityManager->find($className, $id);
191
    }
192
}
193