Passed
Push — master ( 09a4c4...a9ac3c )
by Francis
02:32
created

SwaggerModelGenerator   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 501
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 61
eloc 187
dl 0
loc 501
rs 3.52
c 0
b 0
f 0

24 Methods

Rating   Name   Duplication   Size   Complexity  
A generate() 0 13 4
A __construct() 0 9 1
A generateClass() 0 42 5
A configure() 0 6 1
A configureConstructorBuilder() 0 8 1
A getHelper() 0 3 1
A configurePropertiesBuilder() 0 22 2
A getClassNameAndNamespaceFromDefinitionName() 0 15 2
A getPhpTypeFromPropertyConfig() 0 19 4
A hasDefinition() 0 3 1
A processProperty() 0 45 5
A configureClassBuilder() 0 20 3
A findPropertyTypes() 0 20 3
A getArrayEmbeddedObjectConfig() 0 15 4
A foundNotInheritedProperties() 0 16 4
A getDefinitions() 0 3 1
A flattenRequiresDefinition() 0 20 4
A generateSubClass() 0 31 6
A isOverwrite() 0 3 1
A setOverwrite() 0 3 1
A setDefinitions() 0 3 1
A getFilePathFromDefinitionName() 0 3 1
A flattenPropertiesDefinition() 0 20 4
A setHelper() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like SwaggerModelGenerator 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.

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 SwaggerModelGenerator, and based on these observations, apply Extract Interface, too.

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): ?ClassGeneratorInterface
92
    {
93
        $filePath = $this->getFilePathFromDefinitionName($definitionName);
94
        if (!$this->overwrite && is_file($filePath)) {
95
            return null;
96
        }
97
98
        // Class
99
        $classBuilder = $this->classFactory->createClassBuilder();
100
        $this->configureClassBuilder(
101
            $classBuilder,
102
            $definitionName
103
        );
104
105
        // Properties
106
        /** @var ModelPropertiesGeneratorInterface $modelPropertiesBuilder */
107
        $modelPropertiesBuilder = $classBuilder->getPropertiesBuilder();
108
        $this->configurePropertiesBuilder(
109
            $classBuilder,
110
            $modelPropertiesBuilder,
111
            $definitionName
112
        );
113
114
        // Constructor
115
        $constructorBuilder = $this->methodFactory->createModelConstructorBuilder($classBuilder->getUsesBuilder());
116
        $this->configureConstructorBuilder(
117
            $classBuilder->getMethodsBuilder(),
118
            $modelPropertiesBuilder,
119
            $constructorBuilder
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, $classBuilder->generate($this->indent))) {
129
            throw new Exception(sprintf('Unable to generate the class : "%s" !', $filePath));
130
        }
131
132
        return $classBuilder;
133
    }
134
135
    /**
136
     * {@inheritDoc}
137
     *
138
     * @throws Exception
139
     */
140
    public function generateSubClass(
141
        string $currentDefinitionName,
142
        string $currentProperty,
143
        array $currentConfig
144
    ): ?string
145
    {
146
        if (!isset($currentConfig['type'])) {
147
            return null;
148
        }
149
150
        $subConfig = null;
151
        if ($currentConfig['type'] === 'object') {
152
            $subConfig = $currentConfig;
153
        }
154
155
        if ($currentConfig['type'] === 'array') {
156
            $subConfig = $this->getArrayEmbeddedObjectConfig($currentConfig);
157
        }
158
159
        if (null === $subConfig) {
160
            return null;
161
        }
162
163
        $subDefinitionName = sprintf('%s/%s', $currentDefinitionName, ucfirst($currentProperty));
164
        $this->definitions[$subDefinitionName] = $subConfig;
165
        $this->generateClass($subDefinitionName);
166
        [$subNamespace, $subClassName] = $this->getClassNameAndNamespaceFromDefinitionName($subDefinitionName);
167
        $type = '\\' . $subNamespace . '\\' . $subClassName;
168
        $type .= $currentConfig['type'] === 'array' ? '[]' : '';
169
170
        return $type;
171
    }
172
173
    /**
174
     * {@inheritDoc}
175
     */
176
    public function getArrayEmbeddedObjectConfig(array $config): ?array
177
    {
178
        if (!isset($config['items'])) {
179
            return null;
180
        }
181
182
        if (!isset($config['items']['type'])) {
183
            return null;
184
        }
185
186
        if ('object' !== $config['items']['type']) {
187
            return null;
188
        }
189
190
        return $config['items'];
191
    }
192
193
    /**
194
     * {@inheritDoc}
195
     */
196
    public function getClassNameAndNamespaceFromDefinitionName(
197
        string $definitionName,
198
        string $classPrefix = '',
199
        string $classSuffix = ''
200
    ): array
201
    {
202
        $classPath = $this->helper::camelize($definitionName);
203
        $classParts = explode('/', $classPath);
204
        $className = array_pop($classParts);
205
        $namespace = implode('\\', $classParts);
206
        $namespace = $this->namespace . ($namespace === '' ? '' : '\\'.$namespace);
207
208
        return [
209
            $namespace,
210
            $classPrefix . $className . $classSuffix,
211
        ];
212
    }
213
214
    /**
215
     * {@inheritDoc}
216
     */
217
    protected function getFilePathFromDefinitionName(string $definitionName): string
218
    {
219
        return sprintf('%s/%s.php', $this->folder, $this->helper::camelize($definitionName));
220
    }
221
222
    /**
223
     * {@inheritDoc}
224
     */
225
    public function getPhpTypeFromPropertyConfig(array $config): string
226
    {
227
        $type = $this->helper::getPhpTypeFromSwaggerConfiguration($config);
228
229
        if (null === $type) {
230
            return 'null';
231
        }
232
233
        if (false === $this->hasDefinition($type)) {
234
            return $type;
235
        }
236
237
        if (1 === preg_match('#^\\\\#', $type)) {
238
            return $type;
239
        }
240
241
        $singleType = rtrim($type, '[]');
242
        [$propertyNamespace, $propertyClassName] = $this->getClassNameAndNamespaceFromDefinitionName($singleType);
243
        return $propertyNamespace . '\\' . $propertyClassName;
244
    }
245
246
    /**
247
     * {@inheritDoc}
248
     *
249
     * @throws Exception
250
     */
251
    public function configureClassBuilder(
252
        ClassGeneratorInterface $classBuilder,
253
        string $definitionName
254
    ): ?ClassGeneratorInterface
255
    {
256
        $subClassBuilder = null;
257
        $extendClass = null;
258
        if (isset($this->definitions[$definitionName]['allOf'])) {
259
            $allOfConfig = $this->definitions[$definitionName]['allOf'][0];
260
            $subDefinitionName = $this->helper::getPhpTypeFromSwaggerConfiguration($allOfConfig);
261
            if (null === $subDefinitionName) {
262
                return null;
263
            }
264
            $subClassBuilder = $this->generateClass($subDefinitionName);
265
            $extendClass = $this->getPhpTypeFromPropertyConfig($allOfConfig);
266
        }
267
        [$namespace, $className] = $this->getClassNameAndNamespaceFromDefinitionName($definitionName);
268
        $classBuilder->configure($namespace, $className, $extendClass);
269
270
        return $subClassBuilder;
271
    }
272
273
    /**
274
     * {@inheritDoc}
275
     *
276
     * @throws Exception
277
     */
278
    public function configurePropertiesBuilder(
279
        ClassGeneratorInterface $classBuilder,
280
        ModelPropertiesGeneratorInterface $modelPropertiesBuilder,
281
        string $definitionName
282
    ): void
283
    {
284
        $properties = $this->flattenPropertiesDefinition($this->definitions[$definitionName]);
285
        $requires = $this->flattenRequiresDefinition($this->definitions[$definitionName]);
286
        $ownedProperties = $this->foundNotInheritedProperties($this->definitions[$definitionName]);
287
        $inheritedProperties = array_diff(array_keys($properties), array_keys($ownedProperties));
288
289
        foreach ($properties as $propertyName => $configuration) {
290
            $required = false !== array_search($propertyName, $requires);
291
            $inherited = false !== array_search($propertyName, $inheritedProperties);
292
            $this->processProperty(
293
                $modelPropertiesBuilder,
294
                $classBuilder->getMethodsBuilder(),
295
                $definitionName,
296
                $propertyName,
297
                $configuration,
298
                $required,
299
                $inherited
300
            );
301
        }
302
    }
303
304
    /**
305
     * {@inheritDoc}
306
     */
307
    public function flattenPropertiesDefinition(array $definition): array
308
    {
309
        if (isset($definition['properties'])) {
310
            return $definition['properties'];
311
        }
312
313
        if (!isset($definition['allOf'])) {
314
            return [];
315
        }
316
317
        $allOf = $definition['allOf'];
318
        $inheritedPropertyName = $this->helper::getPhpTypeFromSwaggerDefinitionName($allOf[0]['$ref']);
319
320
        $properties = [];
321
        if (isset($allOf[1]['properties'])) {
322
            $properties = $allOf[1]['properties'];
323
        }
324
325
        $inheritedProperties = $this->flattenPropertiesDefinition($this->definitions[$inheritedPropertyName]);
326
        return array_merge($properties, $inheritedProperties);
327
    }
328
329
    /**
330
     * {@inheritDoc}
331
     */
332
    public function flattenRequiresDefinition(array $definition): array
333
    {
334
        if (isset($definition['required'])) {
335
            return $definition['required'];
336
        }
337
338
        if (!isset($definition['allOf'])) {
339
            return [];
340
        }
341
342
        $allOf = $definition['allOf'];
343
        $inheritedPropertyName = $this->helper::getPhpTypeFromSwaggerDefinitionName($allOf[0]['$ref']);
344
345
        $requires = [];
346
        if (isset($allOf[1]['required'])) {
347
            $requires = $allOf[1]['required'];
348
        }
349
350
        $inheritedRequires = $this->flattenRequiresDefinition($this->definitions[$inheritedPropertyName]);
351
        return array_merge($requires, $inheritedRequires);
352
    }
353
354
    public function foundNotInheritedProperties(array $definition): array
355
    {
356
        if (isset($definition['properties'])) {
357
            return $definition['properties'];
358
        }
359
360
        if (!isset($definition['allOf'])) {
361
            return [];
362
        }
363
364
        $allOf = $definition['allOf'];
365
        if (!isset($allOf[1]['properties'])) {
366
            return [];
367
        }
368
369
        return $allOf[1]['properties'];
370
    }
371
372
    /**
373
     * {@inheritDoc}
374
     *
375
     * @throws Exception
376
     */
377
    public function processProperty(
378
        ModelPropertiesGeneratorInterface $modelPropertiesBuilder,
379
        MethodsGeneratorInterface $methodsBuilder,
380
        string $definitionName,
381
        string $propertyName,
382
        array $configuration,
383
        bool $required = false,
384
        bool $inherited = false
385
    ): void {
386
        $types = $this->findPropertyTypes(
387
            $definitionName,
388
            $propertyName,
389
            $configuration
390
        );
391
392
        $cleanPropertyName = $this->helper::cleanStr($propertyName);
393
        $description = isset($configuration['description']) ? $configuration['description'] : null;
394
395
        /** @var ModelPropertyGeneratorInterface $propertyBuilder */
396
        $propertyBuilder = $this->classFactory->createPropertyBuilder(
397
            $modelPropertiesBuilder->getUsesBuilder()
398
        );
399
        $modelPropertiesBuilder->addPropertyFromSwaggerPropertyDefinition(
400
            $propertyBuilder,
401
            $cleanPropertyName,
402
            $types,
403
            $required,
404
            $inherited,
405
            $description
406
        );
407
408
        if ($inherited) {
409
            return;
410
        }
411
412
        $getterSetterBuilder = $this->methodFactory->createPropertyMethodsBuilder(
413
            $methodsBuilder->getUsesBuilder()
414
        );
415
        $definition = &$this->definitions[$definitionName];
416
        $readOnly = isset($definition['readOnly']) && $definition['readOnly'] === 'true';
417
        $writeOnly = isset($definition['writeOnly']) && $definition['writeOnly'] === 'true';
418
        $getterSetterBuilder->configure($propertyBuilder, $readOnly, $writeOnly);
419
420
        $methodBuilders = $getterSetterBuilder->getMethods($this->methodFactory, $this->indent);
421
        $methodsBuilder->addMultipleMethod($methodBuilders);
422
    }
423
424
    /**
425
     * {@inheritDoc}
426
     */
427
    public function configureConstructorBuilder(
428
        MethodsGeneratorInterface $methodsBuilder,
429
        ModelPropertiesGeneratorInterface $modelPropertiesBuilder,
430
        ModelConstructorGeneratorInterface $constructorBuilder
431
    ): void
432
    {
433
        $constructorBuilder->configureFromPropertiesBuilder($modelPropertiesBuilder);
434
        $methodsBuilder->addMethod($constructorBuilder);
435
    }
436
437
    /**
438
     * {@inheritDoc}
439
     *
440
     * @throws Exception
441
     */
442
    public function findPropertyTypes(
443
        string $definitionName,
444
        string $propertyName,
445
        array $configuration
446
    ): array
447
    {
448
        $cleanPropertyName = $this->helper::cleanStr($propertyName);
449
450
        $type = $this->generateSubClass($definitionName, $cleanPropertyName, $configuration);
451
        if (null === $type) {
452
            $type = $this->getPhpTypeFromPropertyConfig($configuration);
453
        }
454
455
        $types = [$type];
456
457
        if ($this->helper::isNullableBySwaggerConfiguration($propertyName, $this->definitions[$definitionName])) {
458
            $types[] = 'null';
459
        }
460
461
        return $types;
462
    }
463
464
    /**
465
     * {@inheritDoc}
466
     */
467
    public function hasDefinition(string $definitionName): bool
468
    {
469
        return isset($this->definitions[$definitionName]);
470
    }
471
472
    /**
473
     * {@inheritDoc}
474
     */
475
    public function getDefinitions(): array
476
    {
477
        return $this->definitions;
478
    }
479
480
    /**
481
     * {@inheritDoc}
482
     */
483
    public function setDefinitions(array $definitions): void
484
    {
485
        $this->definitions = $definitions;
486
    }
487
488
    /**
489
     * {@inheritDoc}
490
     */
491
    public function getHelper(): SwaggerModelHelperInterface
492
    {
493
        return $this->helper;
494
    }
495
496
    /**
497
     * {@inheritDoc}
498
     */
499
    public function setHelper(SwaggerModelHelperInterface $helper): void
500
    {
501
        $this->helper = $helper;
502
    }
503
504
    /**
505
     * {@inheritDoc}
506
     */
507
    public function isOverwrite(): bool
508
    {
509
        return $this->overwrite;
510
    }
511
512
    /**
513
     * {@inheritDoc}
514
     */
515
    public function setOverwrite(bool $overwrite): void
516
    {
517
        $this->overwrite = $overwrite;
518
    }
519
}
520