CodeGenerator::generate()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 13
c 1
b 0
f 0
dl 0
loc 21
rs 9.8333
cc 3
nc 4
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PHPTdGram\SchemaGenerator;
6
7
use InvalidArgumentException;
8
use JsonSerializable;
9
use Nette\PhpGenerator\Literal;
10
use Nette\PhpGenerator\PhpFile;
11
use Nette\PhpGenerator\PsrPrinter;
12
use PHPTdGram\SchemaGenerator\Model\ClassDefinition;
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
    /**
154
     * @param ClassDefinition[] $classes
155
     */
156
    public function generateTdSchemaRegistry(array $classes): PhpFile
157
    {
158
        $phpFile = new PhpFile();
159
        $phpFile->addComment('This phpFile is auto-generated.');
160
        $phpFile->setStrictTypes(); // adds declare(strict_types=1)
161
162
        $phpNamespace = $phpFile->addNamespace($this->baseNamespace);
163
164
        $phpNamespace->addUse(InvalidArgumentException::class);
165
166
        $class = $phpNamespace->addClass(static::SCHEMA_REGISTRY_CLASS);
167
168
        $types = [];
169
170
        foreach ($classes as $classDefinition) {
171
            $types[$classDefinition->typeName] = new Literal($classDefinition->className . '::class');
172
        }
173
174
        $class->addConstant('VERSION', '1.6.0')
175
            ->setPublic();
176
177
        $class->addConstant('TYPES', $types)
178
            ->setPublic();
179
180
        $hasTypeMethod = $class->addMethod('hasType')
181
            ->setPublic()
182
            ->setStatic()
183
            ->setReturnType('bool');
184
185
        $hasTypeMethod->addParameter('type')
186
            ->setType('string');
187
188
        $hasTypeMethod->addBody('return isset(static::TYPES[$type]);');
189
190
        $getTypeClassMethod = $class->addMethod('getTypeClass')
191
            ->setPublic()
192
            ->setStatic()
193
            ->setReturnType('string');
194
195
        $getTypeClassMethod->addParameter('type')
196
            ->setType('string');
197
198
        $getTypeClassMethod->addBody('if (!static::hasType($type)) {');
199
        $getTypeClassMethod->addBody('    throw new InvalidArgumentException(');
200
        $getTypeClassMethod->addBody('        sprintf(\'Type "%s" not found in registry\', $type)');
201
        $getTypeClassMethod->addBody('    );');
202
        $getTypeClassMethod->addBody('}');
203
        $getTypeClassMethod->addBody('');
204
        $getTypeClassMethod->addBody('return static::TYPES[$type];');
205
206
        $fromArrayMethod = $class->addMethod('fromArray')
207
            ->setPublic()
208
            ->setStatic()
209
            ->setReturnType('TdObject');
210
211
        $fromArrayMethod->addParameter('array')
212
            ->setType('array');
213
214
        $fromArrayMethod->addBody('if (!isset($array[\'@type\'])) {');
215
        $fromArrayMethod->addBody('    throw new InvalidArgumentException(\'Can\\\'t find "@type" key in array\');');
216
        $fromArrayMethod->addBody('}');
217
        $fromArrayMethod->addBody('');
218
        $fromArrayMethod->addBody('$type = $array[\'@type\'];');
219
        $fromArrayMethod->addBody('$extra = $array[\'@extra\'] ?? null;');
220
        $fromArrayMethod->addBody('$typeClass = static::getTypeClass($type);');
221
        $fromArrayMethod->addBody('');
222
        $fromArrayMethod->addBody('$obj = call_user_func($typeClass . \'::fromArray\', $array);');
223
        $fromArrayMethod->addBody('');
224
        $fromArrayMethod->addBody('return $obj->setTdExtra($extra);');
225
226
        return $phpFile;
227
    }
228
229
    public function generateClass(ClassDefinition $classDef): PhpFile
230
    {
231
        $phpFile = new PhpFile();
232
        $phpFile->addComment('This phpFile is auto-generated.');
233
        $phpFile->setStrictTypes(); // adds declare(strict_types=1)
234
235
        $phpNamespace = $phpFile->addNamespace($this->baseNamespace);
236
237
        $class = $phpNamespace->addClass($classDef->className);
238
239
        $parentClass = $classDef->parentClass;
240
        if ('Object' === $parentClass) {
241
            $parentClass = static::OBJECT_CLASS;
242
        } elseif ('Function' === $parentClass) {
243
            $parentClass = static::FUNCTION_CLASS;
244
        }
245
246
        $class->addExtend($parentClass)
247
            ->addComment($classDef->classDocs);
248
249
        $class->addConstant('TYPE_NAME', $classDef->typeName)
250
            ->setPublic();
251
252
        $constructor = $class->addMethod('__construct')
253
            ->setPublic();
254
255
        if (!in_array($parentClass, [static::OBJECT_CLASS, static::FUNCTION_CLASS])) {
256
            $constructor->addBody('parent::__construct();')
257
                ->addBody('');
258
        }
259
260
        $fromArray = $class->addMethod('fromArray')
261
            ->setPublic()
262
            ->setStatic()
263
            ->setReturnType($classDef->className);
264
265
        $serialize = $class->addMethod('typeSerialize')
266
            ->setReturnType('array');
267
268
        $fromArray->addParameter('array')
269
            ->setType('array');
270
271
        if (count($classDef->fields) > 0) {
272
            $fromArray->addBody('return new static(');
273
274
            $serialize->addBody('return [');
275
            $serialize->addBody('    \'@type\' => static::TYPE_NAME,');
276
        } else {
277
            $fromArray->addBody('return new static();');
278
            $serialize->addBody('return [\'@type\' => static::TYPE_NAME];');
279
        }
280
281
        foreach ($classDef->fields as $fieldDef) {
282
            $typeStyle = $fieldDef->type;
283
            $type      = $fieldDef->type;
284
285
            $arrayNestLevels = substr_count($type, '[]');
286
            if (1 === $arrayNestLevels) {
287
                $type      = 'array';
288
                $typeStyle = 'array';
289
            } elseif (2 === $arrayNestLevels) {
290
                $type      = 'array';
291
                $typeStyle = 'array_array';
292
            } elseif ($arrayNestLevels > 2) {
293
                throw new InvalidArgumentException('Vector of higher than 2 lvl deep');
294
            }
295
296
            $class->addProperty($fieldDef->name)
297
                ->setProtected()
298
                ->setNullable($fieldDef->mayBeNull)
299
                ->setType($type)
300
                ->addComment($fieldDef->doc)
301
                ->addComment('')
302
                ->addComment('@var ' . $fieldDef->type . ($fieldDef->mayBeNull ? '|null' : ''));
303
304
            $constructor->addParameter($fieldDef->name)
305
                ->setType($type)
306
                ->setNullable($fieldDef->mayBeNull);
307
308
            $constructor->addBody('$this->' . $fieldDef->name . ' = $' . $fieldDef->name . ';');
309
310
            [$rawType] = explode('[', $fieldDef->type);
311
312
            switch ($rawType) {
313
                case 'string':
314
                case 'int':
315
                case 'bool':
316
                case 'float':
317
                    $fromArray->addBody('    $array[\'' . $fieldDef->rawName . '\'],');
318
                    $serialize->addBody('    \'' . $fieldDef->rawName . '\' => $this->' . $fieldDef->name . ',');
319
                    break;
320
321
                default:
322
                    if ($fieldDef->mayBeNull) {
323
                        if ('array' === $typeStyle) {
324
                            $fromArray->addBody(
325
                                '    (isset($array[\'' . $fieldDef->name .
326
                                '\']) ? array_map(fn($x) => ' . 'TdSchemaRegistry::fromArray($x), $array[\'' .
327
                                $fieldDef->name . '\']) : null),'
328
                            );
329
330
                            $serialize->addBody(
331
                                '    (isset($this->' . $fieldDef->name .
332
                                ') ? array_map(fn($x) => $x->typeSerialize(), $this->' . $fieldDef->name . ') : null),'
333
                            );
334
                        } elseif ('array_array' === $typeStyle) {
335
                            $fromArray->addBody(
336
                                '    (isset($array[\'' . $fieldDef->name .
337
                                '\']) ? array_map(fn($x) => ' .
338
                                'array_map(fn($y) => TdSchemaRegistry::fromArray($y), $x), $array[\'' .
339
                                $fieldDef->name . '\']) : null),'
340
                            );
341
342
                            $serialize->addBody(
343
                                '    (isset($this->' . $fieldDef->name .
344
                                ') ? array_map(fn($x) => array_map(fn($y) => $y->typeSerialize(), $x), $this->' .
345
                                $fieldDef->name . ') : null),'
346
                            );
347
                        } else {
348
                            $fromArray->addBody(
349
                                '    (isset($array[\'' . $fieldDef->rawName . '\']) ? ' .
350
                                'TdSchemaRegistry::fromArray($array[\'' . $fieldDef->rawName . '\']) : null),'
351
                            );
352
353
                            $serialize->addBody(
354
                                '    \'' . $fieldDef->rawName . '\' => (isset($this->' .
355
                                $fieldDef->name . ') ? $this->' . $fieldDef->name . ' : null),'
356
                            );
357
                        }
358
                    } else {
359
                        if ('array' === $typeStyle) {
360
                            $fromArray->addBody(
361
                                '    array_map(fn($x) => TdSchemaRegistry::fromArray($x), $array[\'' .
362
                                $fieldDef->name . '\']),'
363
                            );
364
365
                            $serialize->addBody(
366
                                '    array_map(fn($x) => $x->typeSerialize(), $this->' . $fieldDef->name . '),'
367
                            );
368
                        } elseif ('array_array' === $typeStyle) {
369
                            $fromArray->addBody(
370
                                '    array_map(fn($x) => array_map(fn($y) => TdSchemaRegistry::fromArray($y), $x)' .
371
                                ', $array[\'' . $fieldDef->name . '\']),'
372
                            );
373
374
                            $serialize->addBody(
375
                                '    array_map(fn($x) => array_map(fn($y) => $y->typeSerialize(), $x), $this->' .
376
                                $fieldDef->name . '),'
377
                            );
378
                        } else {
379
                            $fromArray->addBody(
380
                                '    ' . 'TdSchemaRegistry::fromArray($array[\'' . $fieldDef->rawName . '\']),'
381
                            );
382
383
                            $serialize->addBody(
384
                                '    \'' . $fieldDef->rawName . '\' => $this->' . $fieldDef->name . '->typeSerialize(),'
385
                            );
386
                        }
387
                    }
388
            }
389
390
            $getter = $class->addMethod('get' . ucfirst($fieldDef->name))
391
                ->setPublic()
392
                ->setReturnType($type)
393
                ->setReturnNullable($fieldDef->mayBeNull);
394
395
            $getter->addBody('return $this->' . $fieldDef->name . ';');
396
        }
397
398
        if (count($classDef->fields) > 0) {
399
            $fromArray->addBody(');');
400
401
            $serialize->addBody('];');
402
        }
403
404
        return $phpFile;
405
    }
406
}
407