Completed
Push — master ( 5f432b...5d5b15 )
by Jens
15:50 queued 05:48
created

ClassAnnotator   B

Complexity

Total Complexity 54

Size/Duplication

Total Lines 304
Duplicated Lines 18.75 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 1 Features 1
Metric Value
wmc 54
c 2
b 1
f 1
lcom 1
cbo 1
dl 57
loc 304
ccs 0
cts 248
cp 0
rs 7.0642

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
C reflectFields() 0 50 11
A reflectReference() 0 21 3
B reflectElementType() 33 52 7
B reflectResultClass() 12 27 3
A generate() 0 10 2
A generateCurrentMethod() 0 9 2
A generateMapResponseMethod() 0 9 2
A isOptional() 0 10 4
D annotate() 0 51 11
B ignoreDocBlockLine() 12 17 7
A isPrimitive() 0 12 1

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

1
<?php
2
/**
3
 * @author @jayS-de <[email protected]>
4
 */
5
6
namespace Commercetools\Core\Helper\Annotate;
7
8
use Commercetools\Core\Model\Common\JsonObject;
9
use Commercetools\Core\Request\AbstractApiRequest;
10
11
class ClassAnnotator
12
{
13
    /**
14
     * @var ReflectedClass
15
     */
16
    protected $class;
17
18
    protected $fields;
19
20
    public function __construct($className)
21
    {
22
        $this->class = new ReflectedClass($className);
23
    }
24
25
    /**
26
     * @return array
27
     */
28
    protected function reflectFields()
29
    {
30
        $reflectionClass = new \ReflectionClass($this->class->getClassName());
31
        if (!$reflectionClass->hasMethod('fieldDefinitions')) {
32
            return;
33
        }
34
        $reflectionMethod = $reflectionClass->getMethod('fieldDefinitions');
35
36
        $classObject = $reflectionClass->newInstanceWithoutConstructor();
37
        $this->fields = $reflectionMethod->invoke($classObject);
38
39
        foreach ($this->fields as $fieldName => $field) {
40
            $fieldType = '';
41
            if (isset($field[JsonObject::TYPE])) {
42
                $fieldType = $field[JsonObject::TYPE];
43
            }
44
            if (isset($field[JsonObject::DECORATOR])) {
45
                $getReturnType = $field[JsonObject::DECORATOR];
46
                $this->class->addUse($field[JsonObject::DECORATOR]);
47
            } else {
48
                if (empty($fieldType)) {
49
                    $getReturnType = 'mixed';
50
                } else {
51
                    $getReturnType = $fieldType;
52
                }
53
            }
54
            $getReturnTypeParts = explode('\\', trim($getReturnType, '\\'));
55
            if (!$this->isPrimitive($fieldType) && count($getReturnTypeParts) > 1) {
56
                $getReturnClassName = array_pop($getReturnTypeParts);
57
            } else {
58
                $getReturnClassName = $getReturnType;
59
            }
60
            if ($this->isOptional($field)) {
61
                $optional = ' = null';
62
            } else {
63
                $optional = '';
64
            }
65
66
            $fieldTypeParts = explode('\\', trim($fieldType, '\\'));
67
            if (!$this->isPrimitive($fieldType) && count($fieldTypeParts) > 1) {
68
                $this->class->addUse($fieldType);
69
                $fieldType = array_pop($fieldTypeParts);
70
            }
71
72
            $args = [trim($fieldType . ' $' . $fieldName . $optional)];
73
74
            $this->class->addMagicGetSetMethod('get', $fieldName, [], $getReturnClassName);
75
            $this->class->addMagicGetSetMethod('set', $fieldName, $args, $this->class->getShortClassName());
76
        }
77
    }
78
79
    protected function reflectReference()
80
    {
81
        $reflectionClass = new \ReflectionClass($this->class->getClassName());
82
        if (!$reflectionClass->isSubclassOf('Commercetools\Core\Model\Common\Resource')) {
83
            return;
84
        }
85
86
        $referenceClass = $this->class->getClassName() . 'Reference';
87
        $referenceShortName = $this->class->getShortClassName() . 'Reference';
88
        if (class_exists($referenceClass)) {
89
            $this->class->addMagicMethod(
90
                'getReference',
91
                [],
92
                $referenceShortName,
93
                null,
94
                null,
95
                false,
96
                true
97
            );
98
        }
99
    }
100
101
    protected function reflectElementType()
102
    {
103
        $reflectionClass = new \ReflectionClass($this->class->getClassName());
104
        if (!$reflectionClass->hasMethod('getType')) {
105
            return;
106
        }
107
        $reflectionMethod = $reflectionClass->getMethod('getType');
108
109
        $classObject = $reflectionClass->newInstanceWithoutConstructor();
110
        $elementType = $reflectionMethod->invoke($classObject);
111
112
        if ($elementType && !$this->isPrimitive($elementType)) {
113
            $elementTypeClass = new \ReflectionClass($elementType);
114
            $this->class->addUse($elementType);
115
            $getAtMethod = $reflectionClass->getMethod('getAt');
116 View Code Duplication
            if ($getAtMethod->getDeclaringClass()->getName() != $this->class->getClassName()) {
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...
117
                $this->class->addMagicMethod(
118
                    'getAt',
119
                    ['$offset'],
120
                    $elementTypeClass->getShortName(),
121
                    null,
122
                    null,
123
                    false,
124
                    true
125
                );
126
            }
127
            $addMethod = $reflectionClass->getMethod('add');
128 View Code Duplication
            if ($addMethod->getDeclaringClass()->getName() != $this->class->getClassName()) {
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...
129
                $this->class->addMagicMethod(
130
                    'add',
131
                    [$elementTypeClass->getShortName() . ' $element'],
132
                    $reflectionClass->getShortName(),
133
                    null,
134
                    null,
135
                    false,
136
                    true
137
                );
138
            }
139
            $current = $reflectionClass->getMethod('current');
140 View Code Duplication
            if ($current->getDeclaringClass()->getName() != $this->class->getClassName()) {
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...
141
                $this->class->addMagicMethod(
142
                    'current',
143
                    [],
144
                    $elementTypeClass->getShortName(),
145
                    null,
146
                    null,
147
                    false,
148
                    true
149
                );
150
            }
151
        }
152
    }
153
154
    protected function reflectResultClass()
155
    {
156
        $reflectionClass = new \ReflectionClass($this->class->getClassName());
157
        if (!$reflectionClass->hasMethod('getResultClass')) {
158
            return;
159
        }
160
        $reflectionMethod = $reflectionClass->getMethod('getResultClass');
161
162
        $classObject = $reflectionClass->newInstanceWithoutConstructor();
163
        $resultClass = $reflectionMethod->invoke($classObject);
164
165
        $resultClassReflection = new \ReflectionClass($resultClass);
166
        $this->class->addUse($resultClass);
167
        $mapResponseMethod = $reflectionClass->getMethod('mapResponse');
168 View Code Duplication
        if ($mapResponseMethod->getDeclaringClass()->getName() != $this->class->getClassName()) {
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...
169
            $this->class->addUse('\Commercetools\Core\Response\ApiResponseInterface');
170
            $this->class->addMagicMethod(
171
                'mapResponse',
172
                ['ApiResponseInterface $response'],
173
                $resultClassReflection->getShortName(),
174
                null,
175
                null,
176
                false,
177
                true
178
            );
179
        }
180
    }
181
182
    /**
183
     *
184
     */
185
    public function generate()
186
    {
187
        if ($this->class->isAbstract()) {
188
            return;
189
        }
190
191
        $this->reflectFields();
192
        $this->reflectReference();
193
        $this->annotate();
194
    }
195
196
    public function generateCurrentMethod()
197
    {
198
        if ($this->class->isAbstract()) {
199
            return;
200
        }
201
202
        $this->reflectElementType();
203
        $this->annotate();
204
    }
205
206
    public function generateMapResponseMethod()
207
    {
208
        if ($this->class->isAbstract()) {
209
            return;
210
        }
211
212
        $this->reflectResultClass();
213
        $this->annotate();
214
    }
215
216
    /**
217
     * @param $field
218
     * @return bool
219
     */
220
    protected function isOptional($field)
221
    {
222
        if (!isset($field['optional'])) {
223
            return true;
224
        } elseif (isset($field['optional']) && $field['optional'] == true) {
225
            return true;
226
        }
227
228
        return false;
229
    }
230
231
232
    protected function annotate()
233
    {
234
        $classHead = [];
235
        $classHead[] = 'namespace ' . $this->class->getNamespace() . ';';
236
237
        if (count($this->class->getUses()) > 0) {
238
            $classHead[] = '';
239
        }
240
241
        foreach ($this->class->getUses() as $use) {
242
            $classHead[] = 'use ' . $use['class'] . (isset($use['alias']) ? ' as ' . $use['alias'] : '') . ';';
243
        }
244
        $classHead[] = '';
245
        $classHead[] = '/**';
246
        $classHead[] = ' * @package ' . $this->class->getNamespace();
247
        $docBlockLines = $this->class->getDocBlockLines();
248
        foreach ($docBlockLines as $lineNr => $line) {
249
            if ($this->ignoreDocBlockLine($lineNr, $docBlockLines)) {
250
                continue;
251
            }
252
            $classHead[] = ' *' . (empty($line) ? '' : ' ' . $line);
253
        }
254
255
        foreach ($this->class->getMagicGetSetMethods() as $magicMethod) {
256
            $method = (isset($magicMethod['static']) && $magicMethod['static'] ? 'static ' : '');
257
            $method.= $magicMethod['returnTypeHint'] . ' ' . $magicMethod['name'];
258
            $method.= '(' . implode(', ', $magicMethod['args']) . ')';
259
            $methodString = ' * @method ' . trim($method);
260
261
            if (strlen($methodString) >= 120) {
262
                $classHead[] = ' * @codingStandardsIgnoreStart';
263
                $classHead[] = $methodString;
264
                $classHead[] = ' * @codingStandardsIgnoreEnd';
265
            } else {
266
                $classHead[] = $methodString;
267
            }
268
        }
269
        $classHead[] = ' */';
270
271
        $fileName = $this->class->getFileName();
272
273
        $source = file_get_contents($fileName);
274
275
        $newSource = preg_replace(
276
            '~namespace(.*)class ' . $this->class->getShortClassName() . '~s',
277
            implode(PHP_EOL, $classHead) . PHP_EOL . 'class ' . $this->class->getShortClassName(),
278
            $source
279
        );
280
281
        file_put_contents($fileName, $newSource);
282
    }
283
284
    protected function ignoreDocBlockLine($lineNr, $lines)
285
    {
286 View Code Duplication
        if (isset($lines[$lineNr+1]) &&
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...
287
            strpos($lines[$lineNr], '@codingStandardsIgnoreStart') !== false &&
288
            strpos($lines[$lineNr+1], '@codingStandardsIgnoreEnd') !== false
289
        ) {
290
            return true;
291
        }
292 View Code Duplication
        if (isset($lines[$lineNr-1]) &&
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...
293
            strpos($lines[$lineNr], '@codingStandardsIgnoreEnd') !== false &&
294
            strpos($lines[$lineNr-1], '@codingStandardsIgnoreStart') !== false
295
        ) {
296
            return true;
297
        }
298
299
        return false;
300
    }
301
302
    protected function isPrimitive($type)
303
    {
304
        $primitives = [
305
            'bool' => 'is_bool',
306
            'int' => 'is_int',
307
            'string' => 'is_string',
308
            'float' => 'is_float',
309
            'array' => 'is_array'
310
        ];
311
312
        return isset($primitives[$type]);
313
    }
314
}
315