Failed Conditions
Pull Request — experimental/sf (#31)
by Kentaro
06:59
created

AbstractEntity   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 249
Duplicated Lines 6.43 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
dl 16
loc 249
rs 9.2
c 0
b 0
f 0
wmc 40
lcom 1
cbo 6

13 Methods

Rating   Name   Duplication   Size   Complexity  
A offsetExists() 0 9 4
A offsetSet() 0 3 1
A offsetGet() 0 14 5
A offsetUnset() 0 3 1
B setPropertiesFromArray() 8 24 6
B toArray() 8 33 7
B toNormalizedArray() 0 23 6
A toJSON() 0 4 1
A toXML() 0 7 1
A copyProperties() 0 6 1
A setAnnotationReader() 0 6 1
A getAnnotationReader() 0 8 2
A getEntityIdentifierAsArray() 0 20 4

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 AbstractEntity 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 AbstractEntity, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.co.jp/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Eccube\Entity;
15
16
use Doctrine\Common\Annotations\Reader;
17
use Doctrine\Common\Collections\Collection;
18
use Doctrine\Common\Inflector\Inflector;
19
use Doctrine\ORM\Mapping\Id;
20
use Doctrine\ORM\Mapping\MappedSuperclass;
21
use Doctrine\ORM\Proxy\Proxy;
22
use Symfony\Component\Serializer\Encoder\XmlEncoder;
23
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
24
use Symfony\Component\Serializer\Serializer;
25
26
/** @MappedSuperclass */
27
abstract class AbstractEntity implements \ArrayAccess
28
{
29
    private $AnnotationReader;
30
31
    public function offsetExists($offset)
32
    {
33
        $method = Inflector::classify($offset);
34
35
        return method_exists($this, $method)
36
            || method_exists($this, "get$method")
37
            || method_exists($this, "is$method")
38
            || method_exists($this, "has$method");
39
    }
40
41
    public function offsetSet($offset, $value)
42
    {
43
    }
44
45
    public function offsetGet($offset)
46
    {
47
        $method = Inflector::classify($offset);
48
49
        if (method_exists($this, $method)) {
50
            return $this->$method();
51
        } elseif (method_exists($this, "get$method")) {
52
            return $this->{"get$method"}();
53
        } elseif (method_exists($this, "is$method")) {
54
            return $this->{"is$method"}();
55
        } elseif (method_exists($this, "has$method")) {
56
            return $this->{"has$method"}();
57
        }
58
    }
59
60
    public function offsetUnset($offset)
61
    {
62
    }
63
64
    /**
65
     * 引数の連想配列を元にプロパティを設定します.
66
     * DBから取り出した連想配列を, プロパティへ設定する際に使用します.
67
     *
68
     * @param array $arrProps プロパティの情報を格納した連想配列
69
     * @param \ReflectionClass $parentClass 親のクラス. 本メソッドの内部的に使用します.
70
     * @param string[] $excludeAttribute 除外したいフィールド名の配列
71
     */
72
    public function setPropertiesFromArray(array $arrProps, array $excludeAttribute = [], \ReflectionClass $parentClass = null)
73
    {
74
        $objReflect = null;
0 ignored issues
show
Unused Code introduced by
$objReflect is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
75
        if (is_object($parentClass)) {
76
            $objReflect = $parentClass;
77
        } else {
78
            $objReflect = new \ReflectionClass($this);
79
        }
80
        $arrProperties = $objReflect->getProperties();
81 View Code Duplication
        foreach ($arrProperties as $objProperty) {
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...
82
            $objProperty->setAccessible(true);
83
            $name = $objProperty->getName();
84
            if (in_array($name, $excludeAttribute) || !array_key_exists($name, $arrProps)) {
85
                continue;
86
            }
87
            $objProperty->setValue($this, $arrProps[$name]);
88
        }
89
90
        // 親クラスがある場合は再帰的にプロパティを取得
91
        $parentClass = $objReflect->getParentClass();
92
        if (is_object($parentClass)) {
93
            self::setPropertiesFromArray($arrProps, $excludeAttribute, $parentClass);
94
        }
95
    }
96
97
    /**
98
     * Convert to associative array.
99
     *
100
     * Symfony Serializer Component is expensive, and hard to implementation.
101
     * Use for encoder only.
102
     *
103
     * @param \ReflectionClass $parentClass parent class. Use internally of this method..
104
     * @param array $excludeAttribute Array of field names to exclusion.
105
     *
106
     * @return array
107
     */
108
    public function toArray(array $excludeAttribute = ['__initializer__', '__cloner__', '__isInitialized__', 'AnnotationReader'], \ReflectionClass $parentClass = null)
109
    {
110
        $objReflect = null;
0 ignored issues
show
Unused Code introduced by
$objReflect is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
111
        if (is_object($parentClass)) {
112
            $objReflect = $parentClass;
113
        } else {
114
            $objReflect = new \ReflectionClass($this);
115
        }
116
        $arrProperties = $objReflect->getProperties();
117
        $arrResults = [];
118 View Code Duplication
        foreach ($arrProperties as $objProperty) {
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...
119
            $objProperty->setAccessible(true);
120
            $name = $objProperty->getName();
121
            if (in_array($name, $excludeAttribute)) {
122
                continue;
123
            }
124
            $arrResults[$name] = $objProperty->getValue($this);
125
        }
126
127
        $parentClass = $objReflect->getParentClass();
128
        if (is_object($parentClass)) {
129
            $arrParents = self::toArray($excludeAttribute, $parentClass);
130
            if (!is_array($arrParents)) {
131
                $arrParents = [];
132
            }
133
            if (!is_array($arrResults)) {
134
                $arrResults = [];
135
            }
136
            $arrResults = array_merge($arrParents, $arrResults);
137
        }
138
139
        return $arrResults;
140
    }
141
142
    /**
143
     * Convert to associative array, and normalize to association properties.
144
     *
145
     * The type conversion such as:
146
     * - Datetime ::  W3C datetime format string
147
     * - AbstractEntity :: associative array such as [id => value]
148
     * - PersistentCollection :: associative array of [[id => value], [id => value], ...]
149
     *
150
     * @param array $excludeAttribute Array of field names to exclusion.
151
     *
152
     * @return array
153
     */
154
    public function toNormalizedArray(array $excludeAttribute = ['__initializer__', '__cloner__', '__isInitialized__', 'AnnotationReader'])
155
    {
156
        $arrResult = $this->toArray($excludeAttribute);
157
        foreach ($arrResult as &$value) {
158
            if ($value instanceof \DateTime) {
159
                // see also https://stackoverflow.com/a/17390817/4956633
160
                $value->setTimezone(new \DateTimeZone('UTC'));
161
                $value = $value->format('Y-m-d\TH:i:s\Z');
162
            } elseif ($value instanceof AbstractEntity) {
163
                // Entity の場合は [id => value] の配列を返す
164
                $value = $this->getEntityIdentifierAsArray($value);
165
            } elseif ($value instanceof Collection) {
166
                // Collection の場合は ID を持つオブジェクトの配列を返す
167
                $Collections = $value;
168
                $value = [];
169
                foreach ($Collections as $Child) {
170
                    $value[] = $this->getEntityIdentifierAsArray($Child);
171
                }
172
            }
173
        }
174
175
        return $arrResult;
176
    }
177
178
    /**
179
     * Convert to JSON.
180
     *
181
     * @param array $excludeAttribute Array of field names to exclusion.
182
     *
183
     * @return string
184
     */
185
    public function toJSON(array $excludeAttribute = ['__initializer__', '__cloner__', '__isInitialized__', 'AnnotationReader'])
186
    {
187
        return json_encode($this->toNormalizedArray($excludeAttribute));
188
    }
189
190
    /**
191
     * Convert to XML.
192
     *
193
     * @param array $excludeAttribute Array of field names to exclusion.
194
     *
195
     * @return string
196
     */
197
    public function toXML(array $excludeAttribute = ['__initializer__', '__cloner__', '__isInitialized__', 'AnnotationReader'])
198
    {
199
        $ReflectionClass = new \ReflectionClass($this);
200
        $serializer = new Serializer([new PropertyNormalizer()], [new XmlEncoder($ReflectionClass->getShortName())]);
201
202
        return $serializer->serialize($this->toNormalizedArray($excludeAttribute), 'xml');
203
    }
204
205
    /**
206
     * コピー元のオブジェクトのフィールド名を指定して、同名のフィールドに値をコピー
207
     *
208
     * @param object $srcObject コピー元のオブジェクト
209
     * @param string[] $excludeAttribute 除外したいフィールド名の配列
210
     *
211
     * @return AbstractEntity
212
     */
213
    public function copyProperties($srcObject, array $excludeAttribute = [])
214
    {
215
        $this->setPropertiesFromArray($srcObject->toArray($excludeAttribute), $excludeAttribute);
216
217
        return $this;
218
    }
219
220
    /**
221
     * Set AnnotationReader.
222
     *
223
     * @param Reader $Reader
224
     *
225
     * @return AbstractEntity
226
     */
227
    public function setAnnotationReader(Reader $Reader)
228
    {
229
        $this->AnnotationReader = $Reader;
230
231
        return $this;
232
    }
233
234
    /**
235
     * Get AnnotationReader.
236
     *
237
     * @return Reader
238
     */
239
    public function getAnnotationReader()
240
    {
241
        if ($this->AnnotationReader) {
242
            return $this->AnnotationReader;
243
        }
244
245
        return new \Doctrine\Common\Annotations\AnnotationReader();
246
    }
247
248
    /**
249
     * Convert to Entity of Identity value to associative array.
250
     *
251
     * @param AbstractEntity $Entity
252
     *
253
     * @return array associative array of [[id => value], [id => value], ...]
254
     */
255
    public function getEntityIdentifierAsArray(AbstractEntity $Entity)
256
    {
257
        $Result = [];
258
        $PropReflect = new \ReflectionClass($Entity);
259
        if ($Entity instanceof Proxy) {
260
            // Doctrine Proxy の場合は親クラスを取得
261
            $PropReflect = $PropReflect->getParentClass();
262
        }
263
        $Properties = $PropReflect->getProperties();
264
265
        foreach ($Properties as $Property) {
266
            $anno = $this->getAnnotationReader()->getPropertyAnnotation($Property, Id::class);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $anno is correct as $this->getAnnotationRead...\ORM\Mapping\Id::class) (which targets Doctrine\Common\Annotati...getPropertyAnnotation()) 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...
267
            if ($anno) {
268
                $Property->setAccessible(true);
269
                $Result[$Property->getName()] = $Property->getValue($Entity);
270
            }
271
        }
272
273
        return $Result;
274
    }
275
}
276