Completed
Push — master ( 373749...deacd9 )
by Francis
02:07
created

SwaggerModelGenerator   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 474
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 58
eloc 178
c 2
b 0
f 0
dl 0
loc 474
rs 4.5599

23 Methods

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