Completed
Push — master ( a9ac3c...b00023 )
by Francis
02:09
created

configurePropertiesBuilder()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
c 0
b 0
f 0
dl 0
loc 22
rs 9.7666
cc 2
nc 2
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Prometee\SwaggerClientGenerator\Swagger;
6
7
use Exception;
8
use Prometee\SwaggerClientGenerator\Base\Generator\Object\ClassGeneratorInterface;
9
use Prometee\SwaggerClientGenerator\Base\Generator\Object\Other\MethodsGeneratorInterface;
10
use Prometee\SwaggerClientGenerator\Swagger\Helper\SwaggerModelHelperInterface;
11
use Prometee\SwaggerClientGenerator\Swagger\Generator\Factory\ModelClassFactoryInterface;
12
use Prometee\SwaggerClientGenerator\Swagger\Generator\Factory\ModelMethodFactoryInterface;
13
use Prometee\SwaggerClientGenerator\Swagger\Generator\Model\Attribute\ModelPropertyGeneratorInterface;
14
use Prometee\SwaggerClientGenerator\Swagger\Generator\Model\Method\ModelConstructorGeneratorInterface;
15
use Prometee\SwaggerClientGenerator\Swagger\Generator\Model\Other\ModelPropertiesGeneratorInterface;
16
17
class SwaggerModelGenerator implements SwaggerModelGeneratorInterface
18
{
19
    /** @var ModelClassFactoryInterface */
20
    protected $classFactory;
21
    /** @var ModelMethodFactoryInterface */
22
    protected $methodFactory;
23
    /** @var SwaggerModelHelperInterface */
24
    protected $helper;
25
26
    /** @var string */
27
    protected $folder;
28
    /** @var string */
29
    protected $namespace;
30
    /** @var string */
31
    protected $indent;
32
    /** @var array */
33
    protected $definitions = [];
34
    /** @var bool */
35
    protected $overwrite = false;
36
37
    /**
38
     * @param ModelClassFactoryInterface $classFactory
39
     * @param ModelMethodFactoryInterface $methodFactory
40
     * @param SwaggerModelHelperInterface $helper
41
     */
42
    public function __construct(
43
        ModelClassFactoryInterface $classFactory,
44
        ModelMethodFactoryInterface $methodFactory,
45
        SwaggerModelHelperInterface $helper
46
    )
47
    {
48
        $this->classFactory = $classFactory;
49
        $this->methodFactory = $methodFactory;
50
        $this->helper = $helper;
51
    }
52
53
    /**
54
     * @param string $folder
55
     * @param string $namespace
56
     * @param string $indent
57
     */
58
    public function configure(string $folder, string $namespace, string $indent = '    '): void
59
    {
60
        $this->folder = $folder;
61
        $this->namespace = $namespace;
62
        $this->indent = $indent;
63
        $this->definitions = [];
64
    }
65
66
    /**
67
     * {@inheritDoc}
68
     *
69
     * @throws Exception
70
     */
71
    public function generate(): bool
72
    {
73
        foreach ($this->definitions as $definitionName => $definition) {
74
            if (!isset($definition['type'])) {
75
                return false;
76
            }
77
            if ($definition['type'] !== 'object') {
78
                continue;
79
            }
80
            $this->generateClass($definitionName);
81
        }
82
83
        return true;
84
    }
85
86
    /**
87
     * {@inheritDoc}
88
     *
89
     * @throws Exception
90
     */
91
    public function generateClass(string $definitionName): void
92
    {
93
        $filePath = $this->getFilePathFromDefinitionName($definitionName);
94
        if (!$this->overwrite && is_file($filePath)) {
95
            return;
96
        }
97
98
        // Class
99
        $classGenerator = $this->classFactory->createClassGenerator();
100
        $this->configureClassGenerator(
101
            $classGenerator,
102
            $definitionName
103
        );
104
105
        // Properties
106
        /** @var ModelPropertiesGeneratorInterface $modelPropertiesGenerator */
107
        $modelPropertiesGenerator = $classGenerator->getPropertiesGenerator();
108
        $this->configurePropertiesGenerator(
109
            $classGenerator,
110
            $modelPropertiesGenerator,
111
            $definitionName
112
        );
113
114
        // Constructor
115
        $constructorGenerator = $this->methodFactory->createModelConstructorGenerator($classGenerator->getUsesGenerator());
116
        $this->configureConstructorGenerator(
117
            $classGenerator->getMethodsGenerator(),
118
            $modelPropertiesGenerator,
119
            $constructorGenerator
120
        );
121
122
        // File creation
123
        $directory = dirname($filePath);
124
        if (!is_dir($directory)) {
125
            mkdir($directory, 0777, true);
126
        }
127
128
        if (false === file_put_contents($filePath, $classGenerator->generate($this->indent))) {
129
            throw new Exception(sprintf('Unable to generate the class : "%s" !', $filePath));
130
        }
131
    }
132
133
    /**
134
     * {@inheritDoc}
135
     *
136
     * @throws Exception
137
     */
138
    public function generateSubClass(
139
        string $currentDefinitionName,
140
        string $currentProperty,
141
        array $currentConfig
142
    ): ?string
143
    {
144
        if (!isset($currentConfig['type'])) {
145
            return null;
146
        }
147
148
        $subConfig = null;
149
        if ($currentConfig['type'] === 'object') {
150
            $subConfig = $currentConfig;
151
        }
152
153
        if ($currentConfig['type'] === 'array') {
154
            $subConfig = $this->getArrayEmbeddedObjectConfig($currentConfig);
155
        }
156
157
        if (null === $subConfig) {
158
            return null;
159
        }
160
161
        $subDefinitionName = sprintf('%s/%s', $currentDefinitionName, ucfirst($currentProperty));
162
        $this->definitions[$subDefinitionName] = $subConfig;
163
        $this->generateClass($subDefinitionName);
164
        [$subNamespace, $subClassName] = $this->getClassNameAndNamespaceFromDefinitionName($subDefinitionName);
165
        $type = '\\' . $subNamespace . '\\' . $subClassName;
166
        $type .= $currentConfig['type'] === 'array' ? '[]' : '';
167
168
        return $type;
169
    }
170
171
    /**
172
     * {@inheritDoc}
173
     */
174
    public function getArrayEmbeddedObjectConfig(array $config): ?array
175
    {
176
        if (!isset($config['items'])) {
177
            return null;
178
        }
179
180
        if (!isset($config['items']['type'])) {
181
            return null;
182
        }
183
184
        if ('object' !== $config['items']['type']) {
185
            return null;
186
        }
187
188
        return $config['items'];
189
    }
190
191
    /**
192
     * {@inheritDoc}
193
     */
194
    public function getClassNameAndNamespaceFromDefinitionName(
195
        string $definitionName,
196
        string $classPrefix = '',
197
        string $classSuffix = ''
198
    ): array
199
    {
200
        $classPath = $this->helper::camelize($definitionName);
201
        $classParts = explode('/', $classPath);
202
        $className = array_pop($classParts);
203
        $namespace = implode('\\', $classParts);
204
        $namespace = $this->namespace . ($namespace === '' ? '' : '\\'.$namespace);
205
206
        return [
207
            $namespace,
208
            $classPrefix . $className . $classSuffix,
209
        ];
210
    }
211
212
    /**
213
     * {@inheritDoc}
214
     */
215
    protected function getFilePathFromDefinitionName(string $definitionName): string
216
    {
217
        return sprintf('%s/%s.php', $this->folder, $this->helper::camelize($definitionName));
218
    }
219
220
    /**
221
     * {@inheritDoc}
222
     */
223
    public function getPhpTypeFromPropertyConfig(array $config): string
224
    {
225
        $type = $this->helper::getPhpTypeFromSwaggerConfiguration($config);
226
227
        if (null === $type) {
228
            return 'null';
229
        }
230
231
        if (false === $this->hasDefinition($type)) {
232
            return $type;
233
        }
234
235
        if (1 === preg_match('#^\\\\#', $type)) {
236
            return $type;
237
        }
238
239
        $singleType = rtrim($type, '[]');
240
        [$propertyNamespace, $propertyClassName] = $this->getClassNameAndNamespaceFromDefinitionName($singleType);
241
        return $propertyNamespace . '\\' . $propertyClassName;
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     *
247
     * @throws Exception
248
     */
249
    public function configureClassGenerator(
250
        ClassGeneratorInterface $classGenerator,
251
        string $definitionName
252
    ): void
253
    {
254
        $subClassGenerator = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $subClassGenerator is dead and can be removed.
Loading history...
255
        $extendClass = null;
256
        if (isset($this->definitions[$definitionName]['allOf'])) {
257
            $allOfConfig = $this->definitions[$definitionName]['allOf'][0];
258
            $subDefinitionName = $this->helper::getPhpTypeFromSwaggerConfiguration($allOfConfig);
259
            if (null === $subDefinitionName) {
260
                return;
261
            }
262
            $this->generateClass($subDefinitionName);
263
            $extendClass = $this->getPhpTypeFromPropertyConfig($allOfConfig);
264
        }
265
        [$namespace, $className] = $this->getClassNameAndNamespaceFromDefinitionName($definitionName);
266
        $classGenerator->configure($namespace, $className, $extendClass);
267
    }
268
269
    /**
270
     * {@inheritDoc}
271
     *
272
     * @throws Exception
273
     */
274
    public function configurePropertiesGenerator(
275
        ClassGeneratorInterface $classGenerator,
276
        ModelPropertiesGeneratorInterface $modelPropertiesGenerator,
277
        string $definitionName
278
    ): void
279
    {
280
        $properties = $this->flattenPropertiesDefinition($this->definitions[$definitionName]);
281
        $requires = $this->flattenRequiresDefinition($this->definitions[$definitionName]);
282
        $ownedProperties = $this->foundNotInheritedProperties($this->definitions[$definitionName]);
283
        $inheritedProperties = array_diff(array_keys($properties), array_keys($ownedProperties));
284
285
        foreach ($properties as $propertyName => $configuration) {
286
            $required = false !== array_search($propertyName, $requires);
287
            $inherited = false !== array_search($propertyName, $inheritedProperties);
288
            $this->processProperty(
289
                $modelPropertiesGenerator,
290
                $classGenerator->getMethodsGenerator(),
291
                $definitionName,
292
                $propertyName,
293
                $configuration,
294
                $required,
295
                $inherited
296
            );
297
        }
298
    }
299
300
    /**
301
     * {@inheritDoc}
302
     */
303
    public function flattenPropertiesDefinition(array $definition): array
304
    {
305
        if (isset($definition['properties'])) {
306
            return $definition['properties'];
307
        }
308
309
        if (!isset($definition['allOf'])) {
310
            return [];
311
        }
312
313
        $allOf = $definition['allOf'];
314
        $inheritedPropertyName = $this->helper::getPhpTypeFromSwaggerDefinitionName($allOf[0]['$ref']);
315
316
        $properties = [];
317
        if (isset($allOf[1]['properties'])) {
318
            $properties = $allOf[1]['properties'];
319
        }
320
321
        $inheritedProperties = $this->flattenPropertiesDefinition($this->definitions[$inheritedPropertyName]);
322
        return array_merge($properties, $inheritedProperties);
323
    }
324
325
    /**
326
     * {@inheritDoc}
327
     */
328
    public function flattenRequiresDefinition(array $definition): array
329
    {
330
        if (isset($definition['required'])) {
331
            return $definition['required'];
332
        }
333
334
        if (!isset($definition['allOf'])) {
335
            return [];
336
        }
337
338
        $allOf = $definition['allOf'];
339
        $inheritedPropertyName = $this->helper::getPhpTypeFromSwaggerDefinitionName($allOf[0]['$ref']);
340
341
        $requires = [];
342
        if (isset($allOf[1]['required'])) {
343
            $requires = $allOf[1]['required'];
344
        }
345
346
        $inheritedRequires = $this->flattenRequiresDefinition($this->definitions[$inheritedPropertyName]);
347
        return array_merge($requires, $inheritedRequires);
348
    }
349
350
    public function foundNotInheritedProperties(array $definition): array
351
    {
352
        if (isset($definition['properties'])) {
353
            return $definition['properties'];
354
        }
355
356
        if (!isset($definition['allOf'])) {
357
            return [];
358
        }
359
360
        $allOf = $definition['allOf'];
361
        if (!isset($allOf[1]['properties'])) {
362
            return [];
363
        }
364
365
        return $allOf[1]['properties'];
366
    }
367
368
    /**
369
     * {@inheritDoc}
370
     *
371
     * @throws Exception
372
     */
373
    public function processProperty(
374
        ModelPropertiesGeneratorInterface $modelPropertiesGenerator,
375
        MethodsGeneratorInterface $methodsGenerator,
376
        string $definitionName,
377
        string $propertyName,
378
        array $configuration,
379
        bool $required = false,
380
        bool $inherited = false
381
    ): void {
382
        $types = $this->findPropertyTypes(
383
            $definitionName,
384
            $propertyName,
385
            $configuration
386
        );
387
388
        $cleanPropertyName = $this->helper::cleanStr($propertyName);
389
        $description = isset($configuration['description']) ? $configuration['description'] : null;
390
391
        /** @var ModelPropertyGeneratorInterface $propertyGenerator */
392
        $propertyGenerator = $this->classFactory->createPropertyGenerator(
393
            $modelPropertiesGenerator->getUsesGenerator()
394
        );
395
        $modelPropertiesGenerator->addPropertyFromSwaggerPropertyDefinition(
396
            $propertyGenerator,
397
            $cleanPropertyName,
398
            $types,
399
            $required,
400
            $inherited,
401
            $description
402
        );
403
404
        if ($inherited) {
405
            return;
406
        }
407
408
        $getterSetterGenerator = $this->methodFactory->createPropertyMethodsGenerator(
409
            $methodsGenerator->getUsesGenerator()
410
        );
411
        $definition = &$this->definitions[$definitionName];
412
        $readOnly = isset($definition['readOnly']) && $definition['readOnly'] === 'true';
413
        $writeOnly = isset($definition['writeOnly']) && $definition['writeOnly'] === 'true';
414
        $getterSetterGenerator->configure($propertyGenerator, $readOnly, $writeOnly);
415
416
        $methodGenerators = $getterSetterGenerator->getMethods($this->methodFactory, $this->indent);
417
        $methodsGenerator->addMultipleMethod($methodGenerators);
418
    }
419
420
    /**
421
     * {@inheritDoc}
422
     */
423
    public function configureConstructorGenerator(
424
        MethodsGeneratorInterface $methodsGenerator,
425
        ModelPropertiesGeneratorInterface $modelPropertiesGenerator,
426
        ModelConstructorGeneratorInterface $constructorGenerator
427
    ): void
428
    {
429
        $constructorGenerator->configureFromPropertiesGenerator($modelPropertiesGenerator);
430
        $methodsGenerator->addMethod($constructorGenerator);
431
    }
432
433
    /**
434
     * {@inheritDoc}
435
     *
436
     * @throws Exception
437
     */
438
    public function findPropertyTypes(
439
        string $definitionName,
440
        string $propertyName,
441
        array $configuration
442
    ): array
443
    {
444
        $cleanPropertyName = $this->helper::cleanStr($propertyName);
445
446
        $type = $this->generateSubClass($definitionName, $cleanPropertyName, $configuration);
447
        if (null === $type) {
448
            $type = $this->getPhpTypeFromPropertyConfig($configuration);
449
        }
450
451
        $types = [$type];
452
453
        if ($this->helper::isNullableBySwaggerConfiguration($propertyName, $this->definitions[$definitionName])) {
454
            $types[] = 'null';
455
        }
456
457
        return $types;
458
    }
459
460
    /**
461
     * {@inheritDoc}
462
     */
463
    public function hasDefinition(string $definitionName): bool
464
    {
465
        return isset($this->definitions[$definitionName]);
466
    }
467
468
    /**
469
     * {@inheritDoc}
470
     */
471
    public function getDefinitions(): array
472
    {
473
        return $this->definitions;
474
    }
475
476
    /**
477
     * {@inheritDoc}
478
     */
479
    public function setDefinitions(array $definitions): void
480
    {
481
        $this->definitions = $definitions;
482
    }
483
484
    /**
485
     * {@inheritDoc}
486
     */
487
    public function getHelper(): SwaggerModelHelperInterface
488
    {
489
        return $this->helper;
490
    }
491
492
    /**
493
     * {@inheritDoc}
494
     */
495
    public function setHelper(SwaggerModelHelperInterface $helper): void
496
    {
497
        $this->helper = $helper;
498
    }
499
500
    /**
501
     * {@inheritDoc}
502
     */
503
    public function isOverwrite(): bool
504
    {
505
        return $this->overwrite;
506
    }
507
508
    /**
509
     * {@inheritDoc}
510
     */
511
    public function setOverwrite(bool $overwrite): void
512
    {
513
        $this->overwrite = $overwrite;
514
    }
515
}
516