BaseCommand   C
last analyzed

Complexity

Total Complexity 55

Size/Duplication

Total Lines 462
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 55
eloc 163
c 2
b 0
f 0
dl 0
loc 462
rs 6

24 Methods

Rating   Name   Duplication   Size   Complexity  
A setIncludedTypes() 0 15 5
A runGenerator() 0 11 2
A processObjectData() 0 10 4
A generateOpenApi() 0 33 5
A generateConfig() 0 4 1
A parse() 0 8 2
A generate() 0 15 6
A formatConfigPath() 0 3 1
A createDirs() 0 14 1
A formatGenPathByDir() 0 3 1
A validate() 0 18 6
A actionIndex() 0 7 1
A formatRequestsPath() 0 4 1
A setObjectName() 0 3 1
A formatFuncTestsPath() 0 3 1
A setGenHistory() 0 10 3
A generateModule() 0 4 1
A formatControllersPath() 0 4 1
A formatMigrationsPath() 0 5 1
A getRollbackInputFile() 0 25 5
A setParser() 0 6 3
A formatEntitiesPath() 0 4 1
A formatGenPath() 0 3 1
A setObjectProps() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like BaseCommand 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 BaseCommand, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SoliDry\Controllers;
4
5
use Illuminate\Console\Command;
6
use SoliDry\Blocks\Config;
7
use SoliDry\Blocks\FileManager;
8
use SoliDry\Blocks\Module;
9
use SoliDry\Exceptions\DirectoryException;
10
use SoliDry\Exceptions\SchemaException;
11
use SoliDry\Helpers\Json;
12
use SoliDry\Types\ConsoleInterface;
13
use SoliDry\Types\CustomsInterface;
14
use SoliDry\Types\DirsInterface;
15
use SoliDry\Types\ErrorsInterface;
16
use SoliDry\Types\PhpInterface;
17
use SoliDry\Types\ApiInterface;
18
use Symfony\Component\Yaml\Yaml;
19
20
/**
21
 * Class BaseCommand
22
 *
23
 * @package SoliDry\Controllers
24
 *
25
 * @property Yaml|Json parser
26
 */
27
class BaseCommand extends Command
28
{
29
30
    use GeneratorTrait;
31
32
    // dirs
33
    /**
34
     * @var string
35
     */
36
    public string $appDir = '';
37
38
    /**
39
     * @var string
40
     */
41
    public string $modulesDir = '';
42
43
    /**
44
     * @var string
45
     */
46
    public string $httpDir = '';
47
48
    /**
49
     * @var string
50
     */
51
    public string $controllersDir = '';
52
53
    /**
54
     * @var string
55
     */
56
    public string $formRequestDir = '';
57
58
    /**
59
     * @var string
60
     */
61
    public string $entitiesDir = '';
62
63
    /**
64
     * @var string
65
     */
66
    public string $migrationsDir = '';
67
68
    public $version;
69
70
    /**
71
     * @var string
72
     */
73
    public string $objectName = '';
74
75
    /**
76
     * @var string
77
     */
78
    public string $defaultController = 'Default';
79
80
    public $force;
81
82
    /**
83
     * @var array
84
     */
85
    public array $customTypes = [
86
        CustomsInterface::CUSTOM_TYPES_ID,
87
        CustomsInterface::CUSTOM_TYPES_TYPE,
88
        CustomsInterface::CUSTOM_TYPES_RELATIONSHIPS,
89
        CustomsInterface::CUSTOM_TYPE_REDIS,
90
    ];
91
92
    /**
93
     * @var array
94
     */
95
    public array $types = [];
96
97
    /**
98
     * @var array
99
     */
100
    public array $currentTypes = [];
101
102
    /**
103
     * @var array
104
     */
105
    public array $historyTypes = [];
106
107
    /**
108
     * @var array
109
     */
110
    public array $diffTypes = [];
111
112
    /**
113
     * @var array
114
     */
115
    public array $objectProps = [];
116
117
    /**
118
     * @var array
119
     */
120
    public array $relationships = [];
121
122
    /**
123
     * @var array
124
     */
125
    private array $files = [];
126
127
    /**
128
     * @var array
129
     */
130
    public array $excludedSubtypes = [
131
        CustomsInterface::CUSTOM_TYPES_ATTRIBUTES,
132
        CustomsInterface::CUSTOM_TYPES_RELATIONSHIPS,
133
        CustomsInterface::CUSTOM_TYPES_QUERY_PARAMS,
134
        CustomsInterface::CUSTOM_TYPES_FILTER,
135
        CustomsInterface::CUSTOM_TYPES_TREES,
136
    ];
137
138
    /**
139
     * @var array
140
     */
141
    public array $options = [];
142
143
    /**
144
     * @var bool
145
     */
146
    public bool $isMerge = false;
147
148
    /** increment created routes to create file first and then append content */
149
    public int $routesCreated = 0;
150
151
    /**
152
     * @var array
153
     */
154
    public array $data = [];
155
156
    /**
157
     * @var bool
158
     */
159
    public bool $isRollback = false;
160
161
    /**
162
     * @var string
163
     */
164
    private string $parser = Yaml::class;
165
166
    /**
167
     * Generates api components for OAS
168
     *
169
     * @param mixed $files path to openapi file or an array of files in case of rollback
170
     * @throws \Symfony\Component\Yaml\Exception\ParseException
171
     * @throws SchemaException
172
     */
173
    public function actionIndex($files)
174
    {
175
        $this->setParser($files);
176
        $this->parse($files);
177
178
        $this->validate();
179
        $this->generateOpenApi();
180
    }
181
182
    private function setParser($files)
183
    {
184
        $filename = is_array($files) ? $files[0] : $files;
185
        $ext = pathinfo($filename, PATHINFO_EXTENSION);
186
187
        $this->parser = ($ext === 'json') ? Json::class : Yaml::class;
0 ignored issues
show
Documentation Bug introduced by
It seems like $ext === 'json' ? SoliDr...ponent\Yaml\Yaml::class of type string is incompatible with the declared type SoliDry\Helpers\Json|Symfony\Component\Yaml\Yaml of property $parser.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
188
    }
189
190
    /**
191
     * @param $files
192
     */
193
    private function parse($files)
194
    {
195
        if ($this->isRollback) {
196
            $this->files = $files;
197
            $this->data = $this->parser::parse(file_get_contents($this->formatGenPathByDir() . $files[0]));
198
        } else {
199
            $this->files[] = $files;
200
            $this->data = $this->parser::parse(file_get_contents($files));
201
        }
202
    }
203
204
    /**
205
     * Validates OAS + Custom fields
206
     *
207
     * @throws SchemaException
208
     */
209
    private function validate()
210
    {
211
        // required yaml fields will be thrown as Exceptions
212
        if (empty($this->data[ApiInterface::OPEN_API_KEY])) {
213
            throw new SchemaException(ErrorsInterface::CONSOLE_ERRORS[ErrorsInterface::CODE_OPEN_API_KEY],
214
                ErrorsInterface::CODE_OPEN_API_KEY);
215
        }
216
217
        $schemas = $this->data[ApiInterface::API_COMPONENTS][ApiInterface::API_SCHEMAS];
218
        if (empty($schemas[CustomsInterface::CUSTOM_TYPES_ID])
219
            || empty($schemas[CustomsInterface::CUSTOM_TYPES_TYPE])
220
            || empty($schemas[CustomsInterface::CUSTOM_RELATIONSHIPS_DATA_ITEM])) {
221
            throw new SchemaException(ErrorsInterface::CONSOLE_ERRORS[ErrorsInterface::CODE_CUSTOM_TYPES],
222
                ErrorsInterface::CODE_CUSTOM_TYPES);
223
        }
224
225
        if (empty($this->data[ApiInterface::API_INFO])) {
226
            $this->warn(ApiInterface::API_INFO . ': field would be convenient to show users what this API is about');
227
        }
228
    }
229
230
    /**
231
     * Main input point of generator
232
     *
233
     * @throws \Symfony\Component\Yaml\Exception\ParseException
234
     */
235
    private function generateOpenApi()
236
    {
237
        $this->appDir = DirsInterface::APPLICATION_DIR;
238
        $this->controllersDir = DirsInterface::CONTROLLERS_DIR;
239
        $this->entitiesDir = DirsInterface::ENTITIES_DIR;
240
        $this->modulesDir = DirsInterface::MODULES_DIR;
241
        $this->httpDir = DirsInterface::HTTP_DIR;
242
        $this->formRequestDir = DirsInterface::FORM_REQUEST_DIR;
243
        $this->migrationsDir = DirsInterface::MIGRATIONS_DIR;
244
245
        foreach ($this->data[ApiInterface::API_SERVERS] as $server) {
246
            $vars = $server[ApiInterface::API_VARS];
247
            $this->version = $vars[ApiInterface::API_BASE_PATH][ApiInterface::API_DEFAULT];
248
249
            if (env('APP_ENV') === 'dev') { // for test env based on .env
250
                $this->options = [
251
                    ConsoleInterface::OPTION_REGENERATE => 1,
252
                    ConsoleInterface::OPTION_MIGRATIONS => 1,
253
                    ConsoleInterface::OPTION_TESTS      => 1,
254
                ];
255
            } else {
256
                $this->options = $this->options();
257
            }
258
259
            $this->setIncludedTypes();
260
            $this->runGenerator();
261
262
            try {
263
                if ($this->isRollback === false) {
264
                    $this->setGenHistory();
265
                }
266
            } catch (DirectoryException $ex) {
267
                $this->error($ex->getTraceAsString());
268
            }
269
        }
270
    }
271
272
    /**
273
     *  Main generator method - the sequence of methods execution is crucial
274
     *
275
     * @throws \Symfony\Component\Yaml\Exception\ParseException
276
     */
277
    private function runGenerator()
278
    {
279
        if (empty($this->options[ConsoleInterface::OPTION_MERGE]) === false) { // create new or regenerate
280
            $this->setMergedTypes();
281
            $this->isMerge = true;
282
        }
283
284
        $this->generateModule();
285
        $this->generateConfig();
286
287
        $this->generate();
288
    }
289
290
    /**
291
     *  Generates new code or regenerate older with new content
292
     */
293
    private function generate()
294
    {
295
        foreach ($this->types as $objName => $objData) {
296
            if (in_array($objName, $this->customTypes) === false) { // if this is not a custom type generate resources
297
                $excluded = false;
298
                foreach ($this->excludedSubtypes as $type) {
299
                    if (strpos($objName, $type) !== false) {
300
                        $excluded = true;
301
                    }
302
                }
303
                // if the type is among excluded - continue
304
                if ($excluded === true) {
305
                    continue;
306
                }
307
                $this->processObjectData($objName, $objData);
308
            }
309
        }
310
    }
311
312
    /**
313
     * @param string $objName
314
     * @param array $objData
315
     */
316
    private function processObjectData(string $objName, array $objData): void
317
    {
318
        foreach ($objData as $k => $v) {
319
            if ($k === ApiInterface::RAML_PROPS) { // process props
320
                $this->setObjectName($objName);
321
                $this->setObjectProps($v);
322
                if ($this->isMerge === true) {
323
                    $this->mergeResources();
324
                } else {
325
                    $this->generateResources();
326
                }
327
            }
328
        }
329
    }
330
331
    private function generateModule(): void
332
    {
333
        $module = new Module($this);
334
        $module->create();
335
    }
336
337
    private function generateConfig(): void
338
    {
339
        $module = new Config($this);
340
        $module->create();
341
    }
342
343
    /**
344
     * @throws \SoliDry\Exceptions\DirectoryException
345
     */
346
    public function createDirs(): void
347
    {
348
        // create modules dir
349
        FileManager::createPath(FileManager::getModulePath($this));
350
        // create config dir
351
        FileManager::createPath($this->formatConfigPath());
352
        // create Controllers dir
353
        FileManager::createPath($this->formatControllersPath());
354
        // create forms dir
355
        FileManager::createPath($this->formatRequestsPath());
356
        // create mapper dir
357
        FileManager::createPath($this->formatEntitiesPath());
358
        // create migrations dir
359
        FileManager::createPath($this->formatMigrationsPath());
360
    }
361
362
    public function formatControllersPath(): string
363
    {
364
        /** @var Command $this */
365
        return FileManager::getModulePath($this, true) . $this->controllersDir;
366
    }
367
368
    public function formatRequestsPath(): string
369
    {
370
        /** @var Command $this */
371
        return FileManager::getModulePath($this, true) . $this->formRequestDir;
372
    }
373
374
    public function formatEntitiesPath(): string
375
    {
376
        /** @var Command $this */
377
        return FileManager::getModulePath($this) . $this->entitiesDir;
378
    }
379
380
    public function formatMigrationsPath(): string
381
    {
382
        /** @var Command $this */
383
        return FileManager::getModulePath($this) . DirsInterface::DATABASE_DIR . PhpInterface::SLASH
384
            . $this->migrationsDir . PhpInterface::SLASH;
385
    }
386
387
    public function formatConfigPath()
388
    {
389
        return FileManager::getModulePath($this) . DirsInterface::MODULE_CONFIG_DIR . PhpInterface::SLASH;
390
    }
391
392
    public function formatGenPath()
393
    {
394
        return DirsInterface::GEN_DIR . PhpInterface::SLASH . date('Y-m-d') . PhpInterface::SLASH;
395
    }
396
397
    public function formatGenPathByDir(): string
398
    {
399
        return DirsInterface::GEN_DIR . PhpInterface::SLASH . $this->genDir . PhpInterface::SLASH;
400
    }
401
402
    public function formatFuncTestsPath()
403
    {
404
        return DirsInterface::TESTS_DIR . PhpInterface::SLASH . DirsInterface::TESTS_FUNC_DIR;
405
    }
406
407
    /**
408
     * @param string $name
409
     */
410
    private function setObjectName(string $name)
411
    {
412
        $this->objectName = $name;
413
    }
414
415
    private function setObjectProps($props)
416
    {
417
        $this->objectProps = $props;
418
    }
419
420
    private function setIncludedTypes()
421
    {
422
        $this->types = $this->data[ApiInterface::API_COMPONENTS][ApiInterface::API_SCHEMAS];
423
        if (empty($this->data[ApiInterface::RAML_KEY_USES]) === false) {
424
            if ($this->isRollback) {
425
                foreach ($this->files as $file) {
426
                    $fileData = $this->parser::parse(file_get_contents($this->formatGenPathByDir() . $file));
427
                    $this->types += $fileData[ApiInterface::API_COMPONENTS][ApiInterface::API_SCHEMAS];
428
                }
429
            } else {
430
                $files = $this->data[ApiInterface::RAML_KEY_USES];
431
                foreach ($files as $file) {
432
                    $this->files[] = $file;
433
                    $fileData = $this->parser::parse(file_get_contents($file));
434
                    $this->types += $fileData[ApiInterface::API_COMPONENTS][ApiInterface::API_SCHEMAS];
435
                }
436
            }
437
        }
438
    }
439
440
    /**
441
     * @throws \SoliDry\Exceptions\DirectoryException
442
     */
443
    private function setGenHistory()
444
    {
445
        if (empty($this->options[ConsoleInterface::OPTION_NO_HISTORY])) {
446
            // create .gen dir to store raml history
447
            FileManager::createPath($this->formatGenPath());
448
            foreach ($this->files as $file) {
449
                $pathInfo = pathinfo($file);
450
                $dest = $this->formatGenPath() . date('His') . PhpInterface::UNDERSCORE
451
                    . $pathInfo['filename'] . PhpInterface::DOT . $pathInfo['extension'];
452
                copy($file, $dest);
453
            }
454
        }
455
    }
456
457
    /**
458
     * Get files to process within rollback
459
     *
460
     * @return array
461
     * @throws \Symfony\Component\Yaml\Exception\ParseException
462
     * @throws DirectoryException
463
     */
464
    protected function getRollbackInputFile(): array
465
    {
466
        $rollBack = $this->option('rollback');
467
        if ($rollBack === ConsoleInterface::MERGE_DEFAULT_VALUE) {
468
            $this->isRollback = true;
469
            return $this->getLastFiles();
470
        }
471
472
        if (is_numeric($rollBack)) {
473
            $dirs = scandir(DirsInterface::GEN_DIR . DIRECTORY_SEPARATOR, SCANDIR_SORT_DESCENDING);
474
            if ($dirs !== false) {
475
                $this->isRollback = true;
476
                $dirs = array_diff($dirs, DirsInterface::EXCLUDED_DIRS);
477
                return $this->composeStepFiles($dirs, $rollBack);
0 ignored issues
show
Bug introduced by
$rollBack of type string is incompatible with the type integer expected by parameter $step of SoliDry\Controllers\Base...and::composeStepFiles(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

477
                return $this->composeStepFiles($dirs, /** @scrutinizer ignore-type */ $rollBack);
Loading history...
478
            }
479
        }
480
481
        if (strtotime($rollBack) !== false) {
482
            $this->isRollback = true;
483
            $dateTime = explode(PhpInterface::SPACE, $rollBack);
484
485
            return $this->composeTimeFiles($dateTime);
486
        }
487
488
        return [];
489
    }
490
}