Completed
Push — master ( abff32...42164a )
by Francis
02:08
created

SwaggerModelGenerator   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 409
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 45
eloc 147
dl 0
loc 409
rs 8.8
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A generate() 0 13 4
A getPhpTypeFromPropertyConfig() 0 19 4
A getClassNameAndNamespaceFromDefinitionName() 0 15 2
A __construct() 0 9 1
A configure() 0 6 1
A getFilePathFromDefinitionName() 0 3 1
A generateSubClass() 0 31 6
A generateClass() 0 40 5
A getHelper() 0 3 1
A hasDefinition() 0 3 1
A processProperty() 0 45 5
A findPropertyTypes() 0 20 3
A getDefinitions() 0 3 1
A configureConstructorGenerator() 0 8 1
A configureClassGenerator() 0 17 3
A isOverwrite() 0 3 1
A setOverwrite() 0 3 1
A setDefinitions() 0 3 1
A configurePropertiesGenerator() 0 22 2
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\ClassGeneratorInterface;
9
use Prometee\SwaggerClientGenerator\Base\Generator\Other\MethodsGeneratorInterface;
10
use Prometee\SwaggerClientGenerator\Swagger\Factory\ModelClassGeneratorFactoryInterface;
11
use Prometee\SwaggerClientGenerator\Swagger\Factory\ModelMethodGeneratorFactoryInterface;
12
use Prometee\SwaggerClientGenerator\Swagger\Generator\Model\Attribute\ModelPropertyGeneratorInterface;
13
use Prometee\SwaggerClientGenerator\Swagger\Generator\Model\Method\ModelConstructorGeneratorInterface;
14
use Prometee\SwaggerClientGenerator\Swagger\Generator\Model\Other\ModelPropertiesGeneratorInterface;
15
use Prometee\SwaggerClientGenerator\Swagger\Helper\SwaggerModelHelperInterface;
16
17
class SwaggerModelGenerator implements SwaggerModelGeneratorInterface
18
{
19
    /** @var ModelClassGeneratorFactoryInterface */
20
    protected $modelClassGeneratorFactory;
21
    /** @var ModelMethodGeneratorFactoryInterface */
22
    protected $modelMethodGeneratorFactory;
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 ModelClassGeneratorFactoryInterface $modelClassGeneratorFactory
39
     * @param ModelMethodGeneratorFactoryInterface $modelMethodGeneratorFactory
40
     * @param SwaggerModelHelperInterface $helper
41
     */
42
    public function __construct(
43
        ModelClassGeneratorFactoryInterface $modelClassGeneratorFactory,
44
        ModelMethodGeneratorFactoryInterface $modelMethodGeneratorFactory,
45
        SwaggerModelHelperInterface $helper
46
    )
47
    {
48
        $this->modelClassGeneratorFactory = $modelClassGeneratorFactory;
49
        $this->modelMethodGeneratorFactory = $modelMethodGeneratorFactory;
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->modelClassGeneratorFactory->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
        /** @var ModelConstructorGeneratorInterface $modelConstructorGenerator */
116
        $modelConstructorGenerator = $this->modelMethodGeneratorFactory->createModelConstructorGenerator($classGenerator->getUsesGenerator());
117
        $this->configureConstructorGenerator(
118
            $classGenerator->getMethodsGenerator(),
119
            $modelPropertiesGenerator,
120
            $modelConstructorGenerator
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, $classGenerator->generate($this->indent))) {
130
            throw new Exception(sprintf('Unable to generate the class : "%s" !', $filePath));
131
        }
132
    }
133
134
    /**
135
     * {@inheritDoc}
136
     *
137
     * @throws Exception
138
     */
139
    public function generateSubClass(
140
        string $currentDefinitionName,
141
        string $currentProperty,
142
        array $currentConfig
143
    ): ?string
144
    {
145
        if (!isset($currentConfig['type'])) {
146
            return null;
147
        }
148
149
        $subConfig = null;
150
        if ($currentConfig['type'] === 'object') {
151
            $subConfig = $currentConfig;
152
        }
153
154
        if ($currentConfig['type'] === 'array') {
155
            $subConfig = $this->helper::getArrayEmbeddedObjectConfig($currentConfig);
156
        }
157
158
        if (null === $subConfig) {
159
            return null;
160
        }
161
162
        $subDefinitionName = sprintf('%s/%s', $currentDefinitionName, ucfirst($currentProperty));
163
        $this->definitions[$subDefinitionName] = $subConfig;
164
        $this->generateClass($subDefinitionName);
165
        [$subNamespace, $subClassName] = $this->getClassNameAndNamespaceFromDefinitionName($subDefinitionName);
166
        $type = '\\' . $subNamespace . '\\' . $subClassName;
167
        $type .= $currentConfig['type'] === 'array' ? '[]' : '';
168
169
        return $type;
170
    }
171
172
    /**
173
     * {@inheritDoc}
174
     */
175
    public function getClassNameAndNamespaceFromDefinitionName(
176
        string $definitionName,
177
        string $classPrefix = '',
178
        string $classSuffix = ''
179
    ): array
180
    {
181
        $classPath = $this->helper::camelize($definitionName);
182
        $classParts = explode('/', $classPath);
183
        $className = array_pop($classParts);
184
        $namespace = implode('\\', $classParts);
185
        $namespace = $this->namespace . ($namespace === '' ? '' : '\\'.$namespace);
186
187
        return [
188
            $namespace,
189
            $classPrefix . $className . $classSuffix,
190
        ];
191
    }
192
193
    /**
194
     * {@inheritDoc}
195
     */
196
    protected function getFilePathFromDefinitionName(string $definitionName): string
197
    {
198
        return sprintf('%s/%s.php', $this->folder, $this->helper::camelize($definitionName));
199
    }
200
201
    /**
202
     * {@inheritDoc}
203
     */
204
    public function getPhpTypeFromPropertyConfig(array $config): string
205
    {
206
        $type = $this->helper::getPhpTypeFromSwaggerConfiguration($config);
207
208
        if (null === $type) {
209
            return 'null';
210
        }
211
212
        if (false === $this->hasDefinition($type)) {
213
            return $type;
214
        }
215
216
        if (1 === preg_match('#^\\\\#', $type)) {
217
            return $type;
218
        }
219
220
        $singleType = rtrim($type, '[]');
221
        [$propertyNamespace, $propertyClassName] = $this->getClassNameAndNamespaceFromDefinitionName($singleType);
222
        return $propertyNamespace . '\\' . $propertyClassName;
223
    }
224
225
    /**
226
     * {@inheritDoc}
227
     *
228
     * @throws Exception
229
     */
230
    public function configureClassGenerator(
231
        ClassGeneratorInterface $classGenerator,
232
        string $definitionName
233
    ): void
234
    {
235
        $extendClass = null;
236
        if (isset($this->definitions[$definitionName]['allOf'])) {
237
            $allOfConfig = $this->definitions[$definitionName]['allOf'][0];
238
            $subDefinitionName = $this->helper::getPhpTypeFromSwaggerConfiguration($allOfConfig);
239
            if (null === $subDefinitionName) {
240
                return;
241
            }
242
            $this->generateClass($subDefinitionName);
243
            $extendClass = $this->getPhpTypeFromPropertyConfig($allOfConfig);
244
        }
245
        [$namespace, $className] = $this->getClassNameAndNamespaceFromDefinitionName($definitionName);
246
        $classGenerator->configure($namespace, $className, $extendClass);
247
    }
248
249
    /**
250
     * {@inheritDoc}
251
     *
252
     * @throws Exception
253
     */
254
    public function configurePropertiesGenerator(
255
        ClassGeneratorInterface $classGenerator,
256
        ModelPropertiesGeneratorInterface $modelPropertiesGenerator,
257
        string $definitionName
258
    ): void
259
    {
260
        $properties = $this->helper::flattenDefinitionType('properties', $this->definitions, $definitionName);
261
        $requires = $this->helper::flattenDefinitionType('required', $this->definitions, $definitionName);
262
        $ownedProperties = $this->helper::foundNotInheritedProperties($this->definitions[$definitionName]);
263
        $inheritedProperties = array_diff(array_keys($properties), array_keys($ownedProperties));
264
265
        foreach ($properties as $propertyName => $configuration) {
266
            $required = false !== array_search($propertyName, $requires);
267
            $inherited = false !== array_search($propertyName, $inheritedProperties);
268
            $this->processProperty(
269
                $modelPropertiesGenerator,
270
                $classGenerator->getMethodsGenerator(),
271
                $definitionName,
272
                $propertyName,
273
                $configuration,
274
                $required,
275
                $inherited
276
            );
277
        }
278
    }
279
280
    /**
281
     * {@inheritDoc}
282
     *
283
     * @throws Exception
284
     */
285
    public function processProperty(
286
        ModelPropertiesGeneratorInterface $modelPropertiesGenerator,
287
        MethodsGeneratorInterface $methodsGenerator,
288
        string $definitionName,
289
        string $propertyName,
290
        array $configuration,
291
        bool $required = false,
292
        bool $inherited = false
293
    ): void {
294
        $types = $this->findPropertyTypes(
295
            $definitionName,
296
            $propertyName,
297
            $configuration
298
        );
299
300
        $cleanPropertyName = $this->helper::cleanStr($propertyName);
301
        $description = isset($configuration['description']) ? $configuration['description'] : null;
302
303
        /** @var ModelPropertyGeneratorInterface $propertyGenerator */
304
        $propertyGenerator = $this->modelClassGeneratorFactory->createPropertyGenerator(
305
            $modelPropertiesGenerator->getUsesGenerator()
306
        );
307
        $modelPropertiesGenerator->addPropertyFromSwaggerPropertyDefinition(
308
            $propertyGenerator,
309
            $cleanPropertyName,
310
            $types,
311
            $required,
312
            $inherited,
313
            $description
314
        );
315
316
        if ($inherited) {
317
            return;
318
        }
319
320
        $getterSetterGenerator = $this->modelMethodGeneratorFactory->createPropertyMethodsGenerator(
321
            $methodsGenerator->getUsesGenerator()
322
        );
323
        $definition = &$this->definitions[$definitionName];
324
        $readOnly = isset($definition['readOnly']) && $definition['readOnly'] === 'true';
325
        $writeOnly = isset($definition['writeOnly']) && $definition['writeOnly'] === 'true';
326
        $getterSetterGenerator->configure($propertyGenerator, $readOnly, $writeOnly);
327
328
        $methodGenerators = $getterSetterGenerator->getMethods($this->modelMethodGeneratorFactory, $this->indent);
329
        $methodsGenerator->addMultipleMethod($methodGenerators);
330
    }
331
332
    /**
333
     * {@inheritDoc}
334
     */
335
    public function configureConstructorGenerator(
336
        MethodsGeneratorInterface $methodsGenerator,
337
        ModelPropertiesGeneratorInterface $modelPropertiesGenerator,
338
        ModelConstructorGeneratorInterface $constructorGenerator
339
    ): void
340
    {
341
        $constructorGenerator->configureFromPropertiesGenerator($modelPropertiesGenerator);
342
        $methodsGenerator->addMethod($constructorGenerator);
343
    }
344
345
    /**
346
     * {@inheritDoc}
347
     *
348
     * @throws Exception
349
     */
350
    public function findPropertyTypes(
351
        string $definitionName,
352
        string $propertyName,
353
        array $configuration
354
    ): array
355
    {
356
        $cleanPropertyName = $this->helper::cleanStr($propertyName);
357
358
        $type = $this->generateSubClass($definitionName, $cleanPropertyName, $configuration);
359
        if (null === $type) {
360
            $type = $this->getPhpTypeFromPropertyConfig($configuration);
361
        }
362
363
        $types = [$type];
364
365
        if ($this->helper::isNullableBySwaggerConfiguration($propertyName, $this->definitions[$definitionName])) {
366
            $types[] = 'null';
367
        }
368
369
        return $types;
370
    }
371
372
    /**
373
     * {@inheritDoc}
374
     */
375
    public function hasDefinition(string $definitionName): bool
376
    {
377
        return isset($this->definitions[$definitionName]);
378
    }
379
380
    /**
381
     * {@inheritDoc}
382
     */
383
    public function getDefinitions(): array
384
    {
385
        return $this->definitions;
386
    }
387
388
    /**
389
     * {@inheritDoc}
390
     */
391
    public function setDefinitions(array $definitions): void
392
    {
393
        $this->definitions = $definitions;
394
    }
395
396
    /**
397
     * {@inheritDoc}
398
     */
399
    public function getHelper(): SwaggerModelHelperInterface
400
    {
401
        return $this->helper;
402
    }
403
404
    /**
405
     * {@inheritDoc}
406
     */
407
    public function setHelper(SwaggerModelHelperInterface $helper): void
408
    {
409
        $this->helper = $helper;
410
    }
411
412
    /**
413
     * {@inheritDoc}
414
     */
415
    public function isOverwrite(): bool
416
    {
417
        return $this->overwrite;
418
    }
419
420
    /**
421
     * {@inheritDoc}
422
     */
423
    public function setOverwrite(bool $overwrite): void
424
    {
425
        $this->overwrite = $overwrite;
426
    }
427
}
428