Passed
Branch php-scrutinizer (0ac9d8)
by Jens
09:19
created

ClassAnnotator::reflectResultClass()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 34
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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