CodeGenerator::generateTdSchemaRegistry()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 68
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 49
nc 2
nop 1
dl 0
loc 68
rs 9.1127
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace AurimasNiekis\TdLibSchema\Generator;
6
7
use AurimasNiekis\TdLibSchema\Generator\Parser\ClassDefinition;
8
use InvalidArgumentException;
9
use JsonSerializable;
10
use Nette\PhpGenerator\Literal;
11
use Nette\PhpGenerator\PhpFile;
12
use Nette\PhpGenerator\PsrPrinter;
13
14
/**
15
 * @author  Aurimas Niekis <[email protected]>
16
 */
17
class CodeGenerator
18
{
19
    public const OBJECT_CLASS              = 'TdObject';
20
    public const FUNCTION_CLASS            = 'TdFunction';
21
    public const SCHEMA_REGISTRY_CLASS     = 'TdSchemaRegistry';
22
    public const TYPE_SERIALIZER_INTERFACE = 'TdTypeSerializableInterface';
23
24
    private string $baseNamespace;
25
    private string $baseFolder;
26
27
    public function __construct(string $baseNamespace, string $baseFolder)
28
    {
29
        $this->baseNamespace = $baseNamespace;
30
        $this->baseFolder    = $baseFolder;
31
    }
32
33
    /**
34
     * @param ClassDefinition[] $classes
35
     */
36
    public function generate(array $classes): void
37
    {
38
        $files = [
39
            'TdTypeSerializableInterface.php' => $this->generateTypeSerializeInterface(),
40
            'TdObject.php'                    => $this->generateTdObject(),
41
            'TdFunction.php'                  => $this->generateTdFunction(),
42
            'TdSchemaRegistry.php'            => $this->generateTdSchemaRegistry($classes),
43
        ];
44
45
        foreach ($classes as $classDefinition) {
46
            $fileName = $classDefinition->className . '.php';
47
48
            $files[$fileName] = $this->generateClass($classDefinition);
49
        }
50
51
        $printer = new PsrPrinter();
52
        $printer->setTypeResolving(false);
53
        foreach ($files as $fileName => $file) {
54
            $filePath = $this->baseFolder . '/' . $fileName;
55
56
            file_put_contents($filePath, $printer->printFile($file));
57
        }
58
    }
59
60
    public function generateTypeSerializeInterface(): PhpFile
61
    {
62
        $phpFile = new PhpFile();
63
        $phpFile->addComment('This phpFile is auto-generated.');
64
        $phpFile->setStrictTypes(); // adds declare(strict_types=1)
65
66
        $phpNamespace = $phpFile->addNamespace($this->baseNamespace);
67
68
        $typeSerializerInterface = $phpNamespace->addInterface(
69
            static::TYPE_SERIALIZER_INTERFACE
70
        );
71
72
        $typeSerializerInterface->addMethod('typeSerialize')
73
            ->setPublic()
74
            ->setReturnType('array');
75
76
        return $phpFile;
77
    }
78
79
    public function generateTdObject(): PhpFile
80
    {
81
        $phpFile = new PhpFile();
82
        $phpFile->addComment('This phpFile is auto-generated.');
83
        $phpFile->setStrictTypes(); // adds declare(strict_types=1)
84
85
        $phpNamespace = $phpFile->addNamespace($this->baseNamespace);
86
        $phpNamespace->addUse(JsonSerializable::class);
87
88
        $objectClass = $phpNamespace->addClass(static::OBJECT_CLASS);
89
        $objectClass->addImplement(static::TYPE_SERIALIZER_INTERFACE)
90
            ->addImplement(JsonSerializable::class)
91
            ->setAbstract();
92
93
        $objectClass->addConstant('TYPE_NAME', '_tdObject')
94
            ->setPublic();
95
96
        $objectClass->addProperty('tdExtra', new Literal('null'))
97
            ->setType('string')
98
            ->setNullable(true);
99
100
        $extraGetMethod = $objectClass->addMethod('getTdExtra')
101
            ->setPublic()
102
            ->setReturnType('string')
103
            ->setReturnNullable();
104
105
        $objectClass->addMethod('getTdTypeName')
106
            ->setPublic()
107
            ->setReturnType('string')
108
            ->setBody('return static::TYPE_NAME;');
109
110
        $extraGetMethod->addBody('return $this->tdExtra;');
111
112
        $extraSetMethod = $objectClass->addMethod('setTdExtra')
113
            ->setPublic()
114
            ->setReturnType('self');
115
116
        $extraSetMethod->addParameter('tdExtra')
117
            ->setType('string')
118
            ->setNullable();
119
120
        $extraSetMethod->addBody('$this->tdExtra = $tdExtra;');
121
        $extraSetMethod->addBody('');
122
        $extraSetMethod->addBody('return $this;');
123
124
        $jsonSerializeMethod = $objectClass->addMethod('jsonSerialize')
125
            ->setPublic()
126
            ->setReturnType('array');
127
128
        $jsonSerializeMethod->addBody('$output = [];');
129
        $jsonSerializeMethod->addBody('if (null !== $this->tdExtra) {');
130
        $jsonSerializeMethod->addBody('    $output[\'@extra\'] = $this->tdExtra;');
131
        $jsonSerializeMethod->addBody('}');
132
        $jsonSerializeMethod->addBody('');
133
        $jsonSerializeMethod->addBody('return array_merge($output, $this->typeSerialize());');
134
135
        return $phpFile;
136
    }
137
138
    public function generateTdFunction(): PhpFile
139
    {
140
        $phpFile = new PhpFile();
141
        $phpFile->addComment('This phpFile is auto-generated.');
142
        $phpFile->setStrictTypes(); // adds declare(strict_types=1)
143
144
        $phpNamespace = $phpFile->addNamespace($this->baseNamespace);
145
146
        $functionClass = $phpNamespace->addClass(static::FUNCTION_CLASS);
147
        $functionClass->addExtend(static::OBJECT_CLASS)
148
            ->setAbstract();
149
150
        return $phpFile;
151
    }
152
153
    public function generateClass(ClassDefinition $classDef): PhpFile
154
    {
155
        $phpFile = new PhpFile();
156
        $phpFile->addComment('This phpFile is auto-generated.');
157
        $phpFile->setStrictTypes(); // adds declare(strict_types=1)
158
159
        $phpNamespace = $phpFile->addNamespace($this->baseNamespace);
160
161
        $class = $phpNamespace->addClass($classDef->className);
162
163
        $parentClass = $classDef->parentClass;
164
        if ('Object' === $parentClass) {
165
            $parentClass = static::OBJECT_CLASS;
166
        } elseif ('Function' === $parentClass) {
167
            $parentClass = static::FUNCTION_CLASS;
168
        }
169
170
        $class->addExtend($parentClass)
171
            ->addComment($classDef->classDocs);
172
173
        $class->addConstant('TYPE_NAME', $classDef->typeName)
174
            ->setPublic();
175
176
        $constructor = $class->addMethod('__construct')
177
            ->setPublic();
178
179
        if (!in_array($parentClass, [static::OBJECT_CLASS, static::FUNCTION_CLASS])) {
180
            $constructor->addBody('parent::__construct();')
181
                ->addBody('');
182
        }
183
184
        $fromArray = $class->addMethod('fromArray')
185
            ->setPublic()
186
            ->setStatic()
187
            ->setReturnType($classDef->className);
188
189
        $serialize = $class->addMethod('typeSerialize')
190
            ->setReturnType('array');
191
192
        $fromArray->addParameter('array')
193
            ->setType('array');
194
195
        if (count($classDef->fields) > 0) {
196
            $fromArray->addBody('return new static(');
197
198
            $serialize->addBody('return [');
199
            $serialize->addBody('    \'@type\' => static::TYPE_NAME,');
200
        } else {
201
            $fromArray->addBody('return new static();');
202
            $serialize->addBody('return [\'@type\' => static::TYPE_NAME];');
203
        }
204
205
        foreach ($classDef->fields as $fieldDef) {
206
            $typeStyle = $fieldDef->type;
207
            $type      = $fieldDef->type;
208
209
            $arrayNestLevels = substr_count($type, '[]');
210
            if (1 === $arrayNestLevels) {
211
                $type      = 'array';
212
                $typeStyle = 'array';
213
            } elseif (2 === $arrayNestLevels) {
214
                $type      = 'array';
215
                $typeStyle = 'array_array';
216
            } elseif ($arrayNestLevels > 2) {
217
                throw new InvalidArgumentException('Vector of higher than 2 lvl deep');
218
            }
219
220
            $class->addProperty($fieldDef->name)
221
                ->setProtected()
222
                ->setNullable($fieldDef->mayBeNull)
223
                ->setType($type)
224
                ->addComment($fieldDef->doc)
225
                ->addComment('')
226
                ->addComment('@var ' . $fieldDef->type . ($fieldDef->mayBeNull ? '|null' : ''));
227
228
            $constructor->addParameter($fieldDef->name)
229
                ->setType($type)
230
                ->setNullable($fieldDef->mayBeNull);
231
232
            $constructor->addBody('$this->' . $fieldDef->name . ' = $' . $fieldDef->name . ';');
233
234
            [$rawType] = explode('[', $fieldDef->type);
235
236
            switch ($rawType) {
237
                case 'string':
238
                case 'int':
239
                case 'bool':
240
                case 'float':
241
                    $fromArray->addBody('    $array[\'' . $fieldDef->rawName . '\'],');
242
                    $serialize->addBody('    \'' . $fieldDef->rawName . '\' => $this->' . $fieldDef->name . ',');
243
                    break;
244
245
                default:
246
                    if ($fieldDef->mayBeNull) {
247
                        if ('array' === $typeStyle) {
248
                            $fromArray->addBody(
249
                                '    (isset($array[\'' . $fieldDef->name .
250
                                '\']) ? array_map(fn($x) => ' . 'TdSchemaRegistry::fromArray($x), $array[\'' .
251
                                $fieldDef->name . '\']) : null),'
252
                            );
253
254
                            $serialize->addBody(
255
                                '    (isset($this->' . $fieldDef->name .
256
                                ') ? array_map(fn($x) => $x->typeSerialize(), $this->' . $fieldDef->name . ') : null),'
257
                            );
258
                        } elseif ('array_array' === $typeStyle) {
259
                            $fromArray->addBody(
260
                                '    (isset($array[\'' . $fieldDef->name .
261
                                '\']) ? array_map(fn($x) => ' .
262
                                'array_map(fn($y) => TdSchemaRegistry::fromArray($y), $x), $array[\'' .
263
                                $fieldDef->name . '\']) : null),'
264
                            );
265
266
                            $serialize->addBody(
267
                                '    (isset($this->' . $fieldDef->name .
268
                                ') ? array_map(fn($x) => array_map(fn($y) => $y->typeSerialize(), $x), $this->' .
269
                                $fieldDef->name . ') : null),'
270
                            );
271
                        } else {
272
                            $fromArray->addBody(
273
                                '    (isset($array[\'' . $fieldDef->rawName . '\']) ? ' .
274
                                'TdSchemaRegistry::fromArray($array[\'' . $fieldDef->rawName . '\']) : null),'
275
                            );
276
277
                            $serialize->addBody(
278
                                '    \'' . $fieldDef->rawName . '\' => (isset($this->' .
279
                                $fieldDef->name . ') ? $this->' . $fieldDef->name . ' : null),'
280
                            );
281
                        }
282
                    } else {
283
                        if ('array' === $typeStyle) {
284
                            $fromArray->addBody(
285
                                '    array_map(fn($x) => TdSchemaRegistry::fromArray($x), $array[\'' .
286
                                $fieldDef->name . '\']),'
287
                            );
288
289
                            $serialize->addBody(
290
                                '    array_map(fn($x) => $x->typeSerialize(), $this->' . $fieldDef->name . '),'
291
                            );
292
                        } elseif ('array_array' === $typeStyle) {
293
                            $fromArray->addBody(
294
                                '    array_map(fn($x) => array_map(fn($y) => TdSchemaRegistry::fromArray($y), $x)' .
295
                                ', $array[\'' . $fieldDef->name . '\']),'
296
                            );
297
298
                            $serialize->addBody(
299
                                '    array_map(fn($x) => array_map(fn($y) => $y->typeSerialize(), $x), $this->' .
300
                                $fieldDef->name . '),'
301
                            );
302
                        } else {
303
                            $fromArray->addBody(
304
                                '    ' . 'TdSchemaRegistry::fromArray($array[\'' . $fieldDef->rawName . '\']),'
305
                            );
306
307
                            $serialize->addBody(
308
                                '    \'' . $fieldDef->rawName . '\' => $this->' . $fieldDef->name . '->typeSerialize(),'
309
                            );
310
                        }
311
                    }
312
            }
313
314
            $getter = $class->addMethod('get' . ucfirst($fieldDef->name))
315
                ->setPublic()
316
                ->setReturnType($type)
317
                ->setReturnNullable($fieldDef->mayBeNull);
318
319
            $getter->addBody('return $this->' . $fieldDef->name . ';');
320
        }
321
322
        if (count($classDef->fields) > 0) {
323
            $fromArray->addBody(');');
324
325
            $serialize->addBody('];');
326
        }
327
328
        return $phpFile;
329
    }
330
331
    /**
332
     * @param ClassDefinition[] $classes
333
     */
334
    public function generateTdSchemaRegistry(array $classes): PhpFile
335
    {
336
        $phpFile = new PhpFile();
337
        $phpFile->addComment('This phpFile is auto-generated.');
338
        $phpFile->setStrictTypes(); // adds declare(strict_types=1)
339
340
        $phpNamespace = $phpFile->addNamespace($this->baseNamespace);
341
342
        $phpNamespace->addUse(InvalidArgumentException::class);
343
344
        $class = $phpNamespace->addClass(static::SCHEMA_REGISTRY_CLASS);
345
346
        $types = [];
347
348
        foreach ($classes as $classDefinition) {
349
            $types[$classDefinition->typeName] = new Literal($classDefinition->className . '::class');
350
        }
351
352
        $class->addConstant('TYPES', $types)
353
            ->setPublic();
354
355
        $hasTypeMethod = $class->addMethod('hasType')
356
            ->setPublic()
357
            ->setStatic()
358
            ->setReturnType('bool');
359
360
        $hasTypeMethod->addParameter('type')
361
            ->setType('string');
362
363
        $hasTypeMethod->addBody('return isset(static::TYPES[$type]);');
364
365
        $getTypeClassMethod = $class->addMethod('getTypeClass')
366
            ->setPublic()
367
            ->setStatic()
368
            ->setReturnType('string');
369
370
        $getTypeClassMethod->addParameter('type')
371
            ->setType('string');
372
373
        $getTypeClassMethod->addBody('if (!static::hasType($type)) {');
374
        $getTypeClassMethod->addBody('    throw new InvalidArgumentException(');
375
        $getTypeClassMethod->addBody('        sprintf(\'Type "%s" not found in registry\', $type)');
376
        $getTypeClassMethod->addBody('    );');
377
        $getTypeClassMethod->addBody('}');
378
        $getTypeClassMethod->addBody('');
379
        $getTypeClassMethod->addBody('return static::TYPES[$type];');
380
381
        $fromArrayMethod = $class->addMethod('fromArray')
382
            ->setPublic()
383
            ->setStatic()
384
            ->setReturnType('TdObject');
385
386
        $fromArrayMethod->addParameter('array')
387
            ->setType('array');
388
389
        $fromArrayMethod->addBody('if (!isset($array[\'@type\'])) {');
390
        $fromArrayMethod->addBody('    throw new InvalidArgumentException(\'Can\\\'t find "@type" key in array\');');
391
        $fromArrayMethod->addBody('}');
392
        $fromArrayMethod->addBody('');
393
        $fromArrayMethod->addBody('$type = $array[\'@type\'];');
394
        $fromArrayMethod->addBody('$extra = $array[\'@extra\'] ?? null;');
395
        $fromArrayMethod->addBody('$typeClass = static::getTypeClass($type);');
396
        $fromArrayMethod->addBody('');
397
        $fromArrayMethod->addBody('$obj = call_user_func($typeClass . \'::fromArray\', $array);');
398
        $fromArrayMethod->addBody('');
399
        $fromArrayMethod->addBody('return $obj->setTdExtra($extra);');
400
401
        return $phpFile;
402
    }
403
}
404