Failed Conditions
Pull Request — experimental/sf (#29)
by Kentaro
50:12 queued 39:05
created

AbstractEntity   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 249
Duplicated Lines 6.43 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 93.68%

Importance

Changes 0
Metric Value
dl 16
loc 249
rs 9.2
c 0
b 0
f 0
ccs 89
cts 95
cp 0.9368
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\Util\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 321
    public function offsetExists($offset)
32
    {
33 321
        $method = Inflector::classify($offset);
34
35 321
        return method_exists($this, $method)
36 321
            || method_exists($this, "get$method")
37 321
            || method_exists($this, "is$method")
38 321
            || method_exists($this, "has$method");
39
    }
40
41
    public function offsetSet($offset, $value)
42
    {
43
    }
44
45 328
    public function offsetGet($offset)
46
    {
47 328
        $method = Inflector::classify($offset);
48
49 328
        if (method_exists($this, $method)) {
50 91
            return $this->$method();
51 328
        } elseif (method_exists($this, "get$method")) {
52 315
            return $this->{"get$method"}();
53 171
        } elseif (method_exists($this, "is$method")) {
54 170
            return $this->{"is$method"}();
55 70
        } 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 243
    public function setPropertiesFromArray(array $arrProps, array $excludeAttribute = [], \ReflectionClass $parentClass = null)
73
    {
74 243
        $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 243
        if (is_object($parentClass)) {
76 243
            $objReflect = $parentClass;
77
        } else {
78 243
            $objReflect = new \ReflectionClass($this);
79
        }
80 243
        $arrProperties = $objReflect->getProperties();
81 243 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 243
            $objProperty->setAccessible(true);
83 243
            $name = $objProperty->getName();
84 243
            if (in_array($name, $excludeAttribute) || !array_key_exists($name, $arrProps)) {
85 243
                continue;
86
            }
87 243
            $objProperty->setValue($this, $arrProps[$name]);
88
        }
89
90
        // 親クラスがある場合は再帰的にプロパティを取得
91 243
        $parentClass = $objReflect->getParentClass();
92 243
        if (is_object($parentClass)) {
93 243
            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 432
    public function toArray(array $excludeAttribute = ['__initializer__', '__cloner__', '__isInitialized__', 'AnnotationReader'], \ReflectionClass $parentClass = null)
109
    {
110 432
        $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 432
        if (is_object($parentClass)) {
112 432
            $objReflect = $parentClass;
113
        } else {
114 432
            $objReflect = new \ReflectionClass($this);
115
        }
116 432
        $arrProperties = $objReflect->getProperties();
117 432
        $arrResults = [];
118 432 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 432
            $objProperty->setAccessible(true);
120 432
            $name = $objProperty->getName();
121 432
            if (in_array($name, $excludeAttribute)) {
122 431
                continue;
123
            }
124 432
            $arrResults[$name] = $objProperty->getValue($this);
125
        }
126
127 432
        $parentClass = $objReflect->getParentClass();
128 432
        if (is_object($parentClass)) {
129 432
            $arrParents = self::toArray($excludeAttribute, $parentClass);
130 432
            if (!is_array($arrParents)) {
131
                $arrParents = [];
132
            }
133 432
            if (!is_array($arrResults)) {
134
                $arrResults = [];
135
            }
136 432
            $arrResults = array_merge($arrParents, $arrResults);
137
        }
138
139 432
        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 3
    public function toNormalizedArray(array $excludeAttribute = ['__initializer__', '__cloner__', '__isInitialized__', 'AnnotationReader'])
155
    {
156 3
        $arrResult = $this->toArray($excludeAttribute);
157 3
        foreach ($arrResult as &$value) {
158 3
            if ($value instanceof \DateTime) {
159
                // see also https://stackoverflow.com/a/17390817/4956633
160 3
                $value->setTimezone(new \DateTimeZone('UTC'));
161 3
                $value = $value->format('Y-m-d\TH:i:s\Z');
162 3
            } elseif ($value instanceof AbstractEntity) {
163
                // Entity の場合は [id => value] の配列を返す
164
                $value = $this->getEntityIdentifierAsArray($value);
165 3
            } elseif ($value instanceof Collection) {
166
                // Collection の場合は ID を持つオブジェクトの配列を返す
167 3
                $Collections = $value;
168 3
                $value = [];
169 3
                foreach ($Collections as $Child) {
170 3
                    $value[] = $this->getEntityIdentifierAsArray($Child);
171
                }
172
            }
173
        }
174
175 3
        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 1
    public function toJSON(array $excludeAttribute = ['__initializer__', '__cloner__', '__isInitialized__', 'AnnotationReader'])
186
    {
187 1
        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 1
    public function toXML(array $excludeAttribute = ['__initializer__', '__cloner__', '__isInitialized__', 'AnnotationReader'])
198
    {
199 1
        $ReflectionClass = new \ReflectionClass($this);
200 1
        $serializer = new Serializer([new PropertyNormalizer()], [new XmlEncoder($ReflectionClass->getShortName())]);
201
202 1
        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 212
    public function copyProperties($srcObject, array $excludeAttribute = [])
214
    {
215 212
        $this->setPropertiesFromArray($srcObject->toArray($excludeAttribute), $excludeAttribute);
216
217 212
        return $this;
218
    }
219
220
    /**
221
     * Set AnnotationReader.
222
     *
223
     * @param Reader $Reader
224
     *
225
     * @return AbstractEntity
226
     */
227 974
    public function setAnnotationReader(Reader $Reader)
228
    {
229 974
        $this->AnnotationReader = $Reader;
230
231 974
        return $this;
232
    }
233
234
    /**
235
     * Get AnnotationReader.
236
     *
237
     * @return Reader
238
     */
239 3
    public function getAnnotationReader()
240
    {
241 3
        if ($this->AnnotationReader) {
242
            return $this->AnnotationReader;
243
        }
244
245 3
        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 3
    public function getEntityIdentifierAsArray(AbstractEntity $Entity)
256
    {
257 3
        $Result = [];
258 3
        $PropReflect = new \ReflectionClass($Entity);
259 3
        if ($Entity instanceof Proxy) {
260
            // Doctrine Proxy の場合は親クラスを取得
261
            $PropReflect = $PropReflect->getParentClass();
262
        }
263 3
        $Properties = $PropReflect->getProperties();
264
265 3
        foreach ($Properties as $Property) {
266 3
            $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 3
            if ($anno) {
268 3
                $Property->setAccessible(true);
269 3
                $Result[$Property->getName()] = $Property->getValue($Entity);
270
            }
271
        }
272
273 3
        return $Result;
274
    }
275
}
276