Completed
Push — master ( b38ea1...abff32 )
by Francis
02:25
created

processOperationThrowsClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
dl 0
loc 8
rs 10
c 1
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Prometee\SwaggerClientGenerator\Swagger;
6
7
use Exception;
8
use Prometee\SwaggerClientGenerator\Base\Factory\ClassGeneratorFactoryInterface;
9
use Prometee\SwaggerClientGenerator\Base\Generator\ClassGenerator;
10
use Prometee\SwaggerClientGenerator\Base\Generator\ClassGeneratorInterface;
11
use Prometee\SwaggerClientGenerator\Base\Generator\Method\MethodGeneratorInterface;
12
use Prometee\SwaggerClientGenerator\Base\Generator\Method\MethodParameterGeneratorInterface;
13
use Prometee\SwaggerClientGenerator\Swagger\Generator\Operation\OperationsMethodGeneratorInterface;
14
use Prometee\SwaggerClientGenerator\Swagger\Helper\SwaggerOperationsHelperInterface;
15
use Prometee\SwaggerClientGenerator\Swagger\Factory\OperationsMethodGeneratorFactoryInterface;
16
17
class SwaggerOperationsGenerator implements SwaggerOperationsGeneratorInterface
18
{
19
    /** @var ClassGeneratorFactoryInterface */
20
    protected $classGeneratorFactory;
21
    /** @var OperationsMethodGeneratorFactoryInterface */
22
    protected $methodFactory;
23
    /** @var SwaggerOperationsHelperInterface */
24
    protected $helper;
25
26
    /** @var string */
27
    protected $folder = '';
28
    /** @var string */
29
    protected $namespace = '';
30
    /** @var string */
31
    protected $modelNamespace = '';
32
    /** @var string */
33
    protected $indent = '    ';
34
    /** @var array */
35
    protected $paths = [];
36
    /** @var ClassGenerator[] */
37
    protected $classGenerators = [];
38
    /** @var string[] */
39
    protected $throwsClasses = [];
40
    /** @var string|null */
41
    protected $abstractOperationClass;
42
    /** @var bool */
43
    protected $overwrite = false;
44
45
    /**
46
     * @param ClassGeneratorFactoryInterface $classGeneratorFactory
47
     * @param OperationsMethodGeneratorFactoryInterface $methodFactory
48
     * @param SwaggerOperationsHelperInterface $helper
49
     */
50
    public function __construct(
51
        ClassGeneratorFactoryInterface $classGeneratorFactory,
52
        OperationsMethodGeneratorFactoryInterface $methodFactory,
53
        SwaggerOperationsHelperInterface $helper
54
    )
55
    {
56
        $this->classGeneratorFactory = $classGeneratorFactory;
57
        $this->methodFactory = $methodFactory;
58
        $this->helper = $helper;
59
    }
60
61
    /**
62
     * {@inheritDoc}
63
     */
64
    public function configure(string $folder, string $namespace, string $modelNamespace, string $indent = '    ')
65
    {
66
        $this->folder = $folder;
67
        $this->namespace = $namespace;
68
        $this->modelNamespace = $modelNamespace;
69
        $this->indent = $indent;
70
71
        $this->paths = [];
72
        $this->classGenerators = [];
73
        $this->throwsClasses = [];
74
    }
75
76
    /**
77
     * {@inheritDoc}
78
     *
79
     * @throws Exception
80
     */
81
    public function generate(): bool
82
    {
83
        foreach ($this->paths as $path => $operationConfigurations) {
84
            $this->generateClass($path, $operationConfigurations);
85
        }
86
87
        return true;
88
    }
89
90
    /**
91
     * {@inheritDoc}
92
     *
93
     * @throws Exception
94
     */
95
    public function processPaths(array $json): bool
96
    {
97
        if (!isset($json['paths'])) {
98
            return false;
99
        }
100
        foreach ($this->paths as $path => $operationConfigurations) {
101
            $this->generateClass($path, $operationConfigurations);
102
        }
103
104
        return true;
105
    }
106
107
    /**
108
     * {@inheritDoc}
109
     *
110
     * @throws Exception
111
     */
112
    public function generateClass(string $path, array $operationConfigurations): ?ClassGeneratorInterface
113
    {
114
        $filePath = $this->getFilePathFromPath(
115
            $path,
116
            '',
117
            static::CLASS_SUFFIX
118
        );
119
        if (!$this->overwrite && is_file($filePath)) {
120
            return null;
121
        }
122
123
        // Class
124
        $classGenerator = $this->getOrCreateClassGenerator($path, $filePath);
125
126
        // Methods
127
        $this->processOperations($classGenerator, $path, $operationConfigurations);
128
129
        // File creation
130
        $directory = dirname($filePath);
131
        if (!is_dir($directory)) {
132
            mkdir($directory, 0777, true);
133
        }
134
135
        if (false === file_put_contents($filePath, $classGenerator->generate($this->indent))) {
136
            throw new Exception(sprintf('Unable to generate the class : "%s" !', $filePath));
137
        }
138
139
        return $classGenerator;
140
    }
141
142
    /**
143
     * {@inheritDoc}
144
     */
145
    protected function getOrCreateClassGenerator(string $path, string $filePath): ClassGeneratorInterface
146
    {
147
        [$namespace, $className] = $this->getClassNameAndNamespaceFromPath(
148
            $path,
149
            '',
150
            static::CLASS_SUFFIX
151
        );
152
153
        if ($this->hasClassGenerator($filePath)) {
154
            return $this->classGenerators[$filePath];
155
        }
156
157
        $classGenerator = $this->classGeneratorFactory->createClassGenerator();
158
        $classGenerator->configure($namespace, $className);
159
        if ($this->abstractOperationClass !== null) {
160
            $classGenerator->setExtendClass($this->abstractOperationClass);
161
        }
162
163
        $this->classGenerators[$filePath] = $classGenerator;
164
165
        return $classGenerator;
166
    }
167
168
    /**
169
     * {@inheritDoc}
170
     */
171
    protected function hasClassGenerator(string $filePath): bool
172
    {
173
        return isset($this->classGenerators[$filePath]);
174
    }
175
176
    /**
177
     * {@inheritDoc}
178
     */
179
    public function processOperations(
180
        ClassGeneratorInterface $classGenerator,
181
        string $path,
182
        array $operationConfigurations
183
    ): void
184
    {
185
        foreach ($operationConfigurations as $operation => $operationConfiguration) {
186
            if (!is_array($operationConfiguration)) {
187
                continue;
188
            }
189
            $this->processOperation($classGenerator, $path, $operation, $operationConfiguration);
190
        }
191
    }
192
193
    /**
194
     * {@inheritDoc}
195
     */
196
    public function processOperation(
197
        ClassGeneratorInterface $classGenerator,
198
        string $path,
199
        string $operation,
200
        array $operationConfiguration
201
    ): void {
202
203
        $operation = strtolower($operation);
204
        if (!in_array($operation, ['get', 'post', 'put', 'patch', 'delete'])) {
205
            return;
206
        }
207
208
        $returnType = $this->processOperationReturnType($operationConfiguration);
209
210
        $operationMethodGenerator = $this->methodFactory->createOperationMethodGenerator(
211
            $classGenerator->getUsesGenerator()
212
        );
213
        $operationMethodName = $this->helper::getOperationMethodName($path, $operation, $operationConfiguration);
214
        $operationMethodGenerator->setName($operationMethodName);
215
        $operationMethodGenerator->addReturnType($returnType);
216
217
        if (isset($operationConfiguration['description'])) {
218
            $operationMethodGenerator->getPhpDocGenerator()->addDescriptionLine($operationConfiguration['description']);
219
        }
220
        $operationMethodGenerator->getPhpDocGenerator()->addDescriptionLine(
221
            sprintf('path: %s', $path)
222
        );
223
        $operationMethodGenerator->getPhpDocGenerator()->addDescriptionLine(
224
            sprintf('method: %s', strtoupper($operation))
225
        );
226
227
        $operationParameters = [];
228
        if (isset($operationConfiguration['parameters'])) {
229
            $operationParameters = $operationConfiguration['parameters'];
230
            $this->processOperationParameters($classGenerator, $operationMethodGenerator, $operationParameters);
231
        }
232
233
        $this->processOperationThrowsClass($classGenerator, $operationMethodGenerator);
234
235
        $operationMethodGenerator->addMethodBodyFromSwaggerConfiguration(
236
            $path,
237
            $operation,
238
            $operationParameters,
239
            $this->indent
240
        );
241
242
        $classGenerator->getMethodsGenerator()->addMethod($operationMethodGenerator);
243
    }
244
245
    /**
246
     * @param ClassGeneratorInterface $classGenerator
247
     * @param OperationsMethodGeneratorInterface $operationMethodGenerator
248
     */
249
    public function processOperationThrowsClass(
250
        ClassGeneratorInterface $classGenerator,
251
        OperationsMethodGeneratorInterface $operationMethodGenerator
252
    ): void
253
    {
254
        foreach ($this->throwsClasses as $throwsClass => $className) {
255
            $classGenerator->getUsesGenerator()->guessUse($throwsClass);
256
            $operationMethodGenerator->getPhpDocGenerator()->addThrowsLine($className);
257
        }
258
    }
259
260
    /**
261
     * @param array $operationConfiguration
262
     *
263
     * @return string
264
     */
265
    public function processOperationReturnType(array $operationConfiguration): string
266
    {
267
        $returnType = null;
268
        if (isset($operationConfiguration['responses'])) {
269
            $returnType = $this->helper::getReturnType($operationConfiguration['responses']);
270
        }
271
272
        if ($returnType !== null) {
273
            $returnType = $this->getPhpNameFromType($returnType);
274
        } else {
275
            $returnType = 'void';
276
        }
277
278
        return $returnType;
279
    }
280
281
    /**
282
     * {@inheritDoc}
283
     */
284
    public function processOperationParameters(ClassGeneratorInterface $classGenerator, MethodGeneratorInterface $methodGenerator, array $operationParameters): void
285
    {
286
        foreach ($operationParameters as $parameterConfiguration) {
287
            $methodParameterGenerator = $this->createAnOperationParameter($classGenerator, $parameterConfiguration);
288
            if ($methodParameterGenerator === null) {
289
                continue;
290
            }
291
            $methodGenerator->addParameter($methodParameterGenerator);
292
        }
293
    }
294
295
    /**
296
     * {@inheritDoc}
297
     */
298
    public function createAnOperationParameter(ClassGeneratorInterface $classGenerator, array $parameterConfiguration): ?MethodParameterGeneratorInterface
299
    {
300
        if (!isset($parameterConfiguration['name'])) {
301
            return null;
302
        }
303
304
        $type = $this->helper::getPhpTypeFromSwaggerConfiguration($parameterConfiguration);
305
        if ($type !== null) {
306
            $type = $this->getPhpNameFromType($type);
307
        }
308
        $types = [$type];
309
310
        $name = lcfirst($this->helper::cleanStr($parameterConfiguration['name']));
311
312
        $value = $this->buildValueForOperationParameter($parameterConfiguration, $type);
313
314
        $description = '';
315
        if (isset($parameterConfiguration['description'])) {
316
            $description = $parameterConfiguration['description'];
317
        }
318
319
        $methodParameterGenerator = $this->methodFactory->createMethodParameterGenerator(
320
            $classGenerator->getUsesGenerator()
321
        );
322
        $methodParameterGenerator->configure(
323
            $types,
324
            $name,
325
            $value,
326
            false,
327
            $description
328
        );
329
330
        return $methodParameterGenerator;
331
    }
332
333
    /**
334
     * {@inheritDoc}
335
     */
336
    public function buildValueForOperationParameter(array $parameterConfiguration, ?string $type): ?string
337
    {
338
        $value = null;
339
        if (isset($parameterConfiguration['default'])) {
340
            $value = (string) $parameterConfiguration['default'];
341
            if ($type === 'string') {
342
                $value = "'" . addslashes($value) . "'";
343
            }
344
        }
345
346
        if (isset($parameterConfiguration['required'])) {
347
            if ((bool)$parameterConfiguration['required']) {
348
                $value = null;
349
            } else {
350
                $value = $value ?? 'null';
351
            }
352
        }
353
354
        return $value;
355
    }
356
357
    /**
358
     * {@inheritDoc}
359
     */
360
    public function getClassNameAndNamespaceFromPath(string $path, string $classPrefix = '', string $classSuffix = ''): array
361
    {
362
        $classPath = $this->helper::getClassPathFromPath($path);
363
        $classParts = explode('/', $classPath);
364
        $className = array_pop($classParts);
365
        $namespace = implode('\\', $classParts);
366
        $namespace = $this->namespace . ($namespace === '' ? '' : $namespace);
367
368
        return [
369
            $namespace,
370
            $classPrefix . $className . $classSuffix,
371
        ];
372
    }
373
374
    /**
375
     * {@inheritDoc}
376
     */
377
    public function getPhpNameFromType(string $type): string
378
    {
379
        if (preg_match('$^#/definitions/$', $type)) {
380
            $className = preg_replace('$#/definitions/$', '', $type);
381
            $className = $this->helper::camelize($className);
382
            return '\\' . $this->modelNamespace . '\\' . $className;
383
        }
384
385
        return $type;
386
    }
387
388
    /**
389
     * {@inheritDoc}
390
     */
391
    protected function getFilePathFromPath(string $path, string $classPrefix = '', string $classSuffix = ''): string
392
    {
393
        $filePath = sprintf('%s/%s%s%s.php', $this->folder, $classPrefix, $this->helper::getClassPathFromPath($path), $classSuffix);
394
395
        return preg_replace('#/' . $classPrefix . $classSuffix . '\.php$#', $classPrefix . $classSuffix . '.php', $filePath);
396
    }
397
398
    /**
399
     * {@inheritDoc}
400
     */
401
    public function getPaths(): array
402
    {
403
        return $this->paths;
404
    }
405
406
    /**
407
     * {@inheritDoc}
408
     */
409
    public function setPaths(array $paths): void
410
    {
411
        $this->paths = $paths;
412
    }
413
414
    /**
415
     * {@inheritDoc}
416
     */
417
    public function getHelper(): SwaggerOperationsHelperInterface
418
    {
419
        return $this->helper;
420
    }
421
422
    /**
423
     * {@inheritDoc}
424
     */
425
    public function setHelper(SwaggerOperationsHelperInterface $helper): void
426
    {
427
        $this->helper = $helper;
428
    }
429
430
    /**
431
     * {@inheritDoc}
432
     */
433
    public function getThrowsClasses(): array
434
    {
435
        return $this->throwsClasses;
436
    }
437
438
    /**
439
     * {@inheritDoc}
440
     */
441
    public function setThrowsClasses(array $throwsClasses): void
442
    {
443
        $this->throwsClasses = $throwsClasses;
444
    }
445
446
    /**
447
     * {@inheritDoc}
448
     */
449
    public function getAbstractOperationClass(): ?string
450
    {
451
        return $this->abstractOperationClass;
452
    }
453
454
    /**
455
     * {@inheritDoc}
456
     */
457
    public function setAbstractOperationClass(?string $abstractOperationClass): void
458
    {
459
        $this->abstractOperationClass = $abstractOperationClass;
460
    }
461
462
    /**
463
     * {@inheritDoc}
464
     */
465
    public function isOverwrite(): bool
466
    {
467
        return $this->overwrite;
468
    }
469
470
    /**
471
     * {@inheritDoc}
472
     */
473
    public function setOverwrite(bool $overwrite): void
474
    {
475
        $this->overwrite = $overwrite;
476
    }
477
}
478