Completed
Pull Request — master (#63)
by Julien
04:55
created

UnitOfWork   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 235
Duplicated Lines 5.96 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 0
Metric Value
wmc 40
lcom 2
cbo 4
dl 14
loc 235
rs 8.2608
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getDirtyData() 0 4 1
A registerClean() 0 9 2
A getDirtyEntity() 0 7 2
A clear() 0 5 1
C getDirtyFields() 14 76 20
B addIdentifiers() 0 12 6
B findOldRelation() 0 21 5
A getEntityId() 0 8 2

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like UnitOfWork often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use UnitOfWork, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Mapado\RestClientSdk;
4
5
use Mapado\RestClientSdk\Helper\ArrayHelper;
6
use Mapado\RestClientSdk\Mapping;
7
use Mapado\RestClientSdk\Mapping\ClassMetadata;
8
use Mapado\RestClientSdk\Mapping\Relation;
9
10
/**
11
 * UnitOfWork
12
 */
13
class UnitOfWork
14
{
15
    /**
16
     * mapping
17
     *
18
     * @var Mapping
19
     * @access private
20
     */
21
    private $mapping;
22
23
    /**
24
     * storage for every entity retrieved
25
     *
26
     * @var array
27
     */
28
    private $storage;
29
30
    /**
31
     * Constructor.
32
     */
33
    public function __construct(Mapping $mapping)
34
    {
35
        $this->mapping = $mapping;
36
        $this->storage = [];
37
    }
38
39
    /**
40
     * getDirtyData
41
     *
42
     * return the new serialized model with only needed fields to update
43
     *
44
     * @param array $newSerializedModel
45
     * @param array $oldSerializedModel
46
     * @param ClassMetadata $classMetadata
47
     * @access public
48
     * @return array
49
     */
50
    public function getDirtyData(array $newSerializedModel, array $oldSerializedModel, ClassMetadata $classMetadata)
51
    {
52
        return $this->getDirtyFields($newSerializedModel, $oldSerializedModel, $classMetadata);
53
    }
54
55
    /**
56
     * registerClean
57
     *
58
     * @param string $id
59
     * @param object $entity
60
     * @access public
61
     * @return UnitOfWork
62
     */
63
    public function registerClean($id, $entity)
64
    {
65
        if (is_object($entity)) {
66
            $entityStored = clone $entity;
67
            $this->storage[$id] = $entityStored;
68
        }
69
70
        return $this;
71
    }
72
73
    /**
74
     * getDirtyEntity
75
     *
76
     * @param string $id
77
     * @access public
78
     * @return mixed
79
     */
80
    public function getDirtyEntity($id)
81
    {
82
        if (isset($this->storage[$id])) {
83
            return $this->storage[$id];
84
        }
85
        return null;
86
    }
87
88
    /**
89
     * clear
90
     *
91
     * @param string $id
92
     * @access public
93
     * @return UnitOfWork
94
     */
95
    public function clear($id)
96
    {
97
        unset($this->storage[$id]);
98
        return $this;
99
    }
100
101
    /**
102
     * getDirtyFields
103
     *
104
     * compares serialize object and returns only modified fields
105
     *
106
     * @param array $newArrayModel
0 ignored issues
show
Bug introduced by
There is no parameter named $newArrayModel. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
107
     * @param array $oldSerializedModel
108
     * @param ClassMetadata $classMetadata
109
     * @access private
110
     * @return array
111
     */
112
    private function getDirtyFields(array $newSerializedModel, array $oldSerializedModel, ClassMetadata $classMetadata = null)
113
    {
114
        $dirtyFields = [];
115
116
        foreach ($newSerializedModel as $key => $value) {
117
            if (!array_key_exists($key, $oldSerializedModel)) {
118
                // a new key has been found, add it to the dirtyFields
119
                $dirtyFields[$key] = $value;
120
                continue;
121
            }
122
123
            $oldValue = $oldSerializedModel[$key];
124
125
            $currentRelation = $classMetadata ? $classMetadata->getRelation($key) : null;
126
127
            if (!$currentRelation) {
128
                if (is_array($value) && !ArrayHelper::arraySame($value, $oldValue)
129
                    || $value != $oldValue
130
                ) {
131
                    $dirtyFields[$key] = $value;
132
                }
133
                continue;
134
            }
135
136
            $currentClassMetadata = $this->mapping->getClassMetadata($currentRelation->getTargetEntity());
137
138
            $idSerializedKey = $currentClassMetadata ? $currentClassMetadata->getIdSerializeKey() : null;
139
140
            if ($currentRelation->getType() === Relation::MANY_TO_ONE) {
141
                if ($value !== $oldValue) {
142
                    if (is_string($value) || is_string($oldValue)) {
143
                        $dirtyFields[$key] = $value;
144 View Code Duplication
                    } else {
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...
145
                        $recursiveDiff = $this->getDirtyFields($value, $oldValue, $currentClassMetadata);
146
147
                        if (!empty($recursiveDiff)) {
148
                            $recursiveDiff[$idSerializedKey] = static::getEntityId($value, $idSerializedKey);
0 ignored issues
show
Bug introduced by
Since getEntityId() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getEntityId() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
149
                            $dirtyFields[$key] = $recursiveDiff;
150
                        }
151
                    }
152
                }
153
154
                continue;
155
            }
156
157
            // ONE_TO_MANY relation
158
159
            if (count($value) != count($oldValue)) {
160
                // get all objects ids of new array
161
                $dirtyFields[$key] = $this->addIdentifiers($value, [], $idSerializedKey);
162
            }
163
164
            foreach ($value as $relationKey => $relationValue) {
165
                $oldRelationValue = $this->findOldRelation($relationValue, $oldValue, $currentClassMetadata);
166
167
168
                if ($relationValue !== $oldRelationValue) {
169
                    if (is_string($relationValue) || is_string($oldRelationValue)) {
170
                        $dirtyFields[$key][$relationKey] = $relationValue;
171
                    } else {
172
                        $recursiveDiff = $this->getDirtyFields($relationValue, $oldRelationValue, $currentClassMetadata);
173
174 View Code Duplication
                        if (!empty($recursiveDiff)) {
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...
175
                            $idSerializedKey = $currentClassMetadata->getIdSerializeKey();
176
177
                            $recursiveDiff[$idSerializedKey] = static::getEntityId($relationValue, $idSerializedKey);
0 ignored issues
show
Bug introduced by
Since getEntityId() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getEntityId() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
178
                            $dirtyFields[$key][$relationKey] = $recursiveDiff;
179
                        }
180
                    }
181
                }
182
            }
183
        }
184
185
186
        return $dirtyFields;
187
    }
188
189
    /**
190
     * addIdentifiers
191
     *
192
     * add defined identifiers to given model
193
     *
194
     * @param array $newSerializedModel
195
     * @param array $dirtyFields
196
     * @access private
197
     * @return array
198
     */
199
    private function addIdentifiers($newSerializedModel, $dirtyFields, $idSerializedKey = null)
200
    {
201
        foreach ($newSerializedModel as $key => $value) {
202
            if ($idSerializedKey && isset($value[$idSerializedKey])) {
203
                $dirtyFields[$key][$idSerializedKey] = $value[$idSerializedKey];
204
            } elseif (is_string($value) && is_int($key)) {
205
                $dirtyFields[$key] = $value;
206
            }
207
        }
208
209
        return $dirtyFields;
210
    }
211
212
    private function findOldRelation($relationValue, array $oldValue, ClassMetadata $classMetadata)
213
    {
214
        $idSerializedKey = $classMetadata->getIdSerializeKey();
215
216
        $relationValueId = is_string($relationValue)
217
            ? $relationValue
218
            : $relationValue[$idSerializedKey];
219
220
        foreach ($oldValue as $oldRelationValue) {
221
            $oldRelationValueId = is_string($oldRelationValue)
222
                 ? $oldRelationValue
223
                 : $oldRelationValue[$idSerializedKey]
224
            ;
225
226
            if ($relationValueId === $oldRelationValueId) {
227
                return $oldRelationValue;
228
            }
229
        }
230
231
        return [];
232
    }
233
234
    /**
235
     * get entity id from string or array
236
     * @param mixed $stringOrEntity
237
     * @param string $idSerializedKey
238
     */
239
    private static function getEntityId($stringOrEntity, $idSerializedKey)
240
    {
241
        if (!is_array($stringOrEntity)) {
242
            return $stringOrEntity;
243
        }
244
245
        return $stringOrEntity[$idSerializedKey];
246
    }
247
}
248