Completed
Push — master ( 1d2d63...5f432b )
by Jens
09:03
created

ClassAnnotator   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 300
Duplicated Lines 19 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 53
c 1
b 0
f 1
lcom 1
cbo 1
dl 57
loc 300
ccs 0
cts 244
cp 0
rs 7.4757

12 Methods

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