Passed
Push — master ( 813b52...b69cbb )
by Arthur
03:02
created

BaseCommand   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 380
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 55
eloc 167
dl 0
loc 380
rs 6
c 0
b 0
f 0

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
    public $rootDir = '';
34
    public $appDir = '';
35
    public $modulesDir = '';
36
    public $httpDir = '';
37
    public $controllersDir = '';
38
    public $formRequestDir = '';
39
    public $entitiesDir = '';
40
    public $migrationsDir = '';
41
42
    public $version;
43
    public $objectName = '';
44
    public $defaultController = 'Default';
45
    public $uriNamedParams;
46
    public $force;
47
    public $customTypes = [
48
        CustomsInterface::CUSTOM_TYPES_ID,
49
        CustomsInterface::CUSTOM_TYPES_TYPE,
50
        CustomsInterface::CUSTOM_TYPES_RELATIONSHIPS,
51
        CustomsInterface::CUSTOM_TYPE_REDIS,
52
    ];
53
54
    public $types = [];
55
    public $currentTypes = [];
56
    public $historyTypes = [];
57
    public $mergedTypes = [];
58
    public $diffTypes = [];
59
    public $objectProps = [];
60
    public $generatedFiles = [];
61
    public $relationships = [];
62
    private $files = [];
63
64
    public $excludedSubtypes = [
65
        CustomsInterface::CUSTOM_TYPES_ATTRIBUTES,
66
        CustomsInterface::CUSTOM_TYPES_RELATIONSHIPS,
67
        CustomsInterface::CUSTOM_TYPES_QUERY_PARAMS,
68
        CustomsInterface::CUSTOM_TYPES_FILTER,
69
        CustomsInterface::CUSTOM_TYPES_TREES,
70
    ];
71
72
    public $options = [];
73
    public $isMerge = false;
74
    /** increment created routes to create file first and then append content */
75
    public $routesCreated = 0;
76
77
    public $data = [];
78
79
    public $isRollback = false;
80
81
    // parser class
82
    private $parser = Yaml::class;
83
84
    /**
85
     * Generates api components for OAS
86
     *
87
     * @param mixed $files path to openapi file or an array of files in case of rollback
88
     * @throws \Symfony\Component\Yaml\Exception\ParseException
89
     * @throws SchemaException
90
     */
91
    public function actionIndex($files)
92
    {
93
        $this->setParser($files);
94
        $this->parse($files);
95
96
        $this->validate();
97
        $this->generateOpenApi();
98
    }
99
100
    private function setParser($files)
101
    {
102
        $filename = is_array($files) ? $files[0] : $files;
103
        $ext = pathinfo($filename, PATHINFO_EXTENSION);
104
105
        $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...
106
    }
107
108
    /**
109
     * @param $files
110
     */
111
    private function parse($files)
112
    {
113
        if ($this->isRollback) {
114
            $this->files = $files;
115
            $this->data = $this->parser::parse(file_get_contents($this->formatGenPathByDir() . $files[0]));
116
        } else {
117
            $this->files[] = $files;
118
            $this->data = $this->parser::parse(file_get_contents($files));
119
        }
120
    }
121
122
    /**
123
     * Validates OAS + Custom fields
124
     *
125
     * @throws SchemaException
126
     */
127
    private function validate()
128
    {
129
        // required yaml fields will be thrown as Exceptions
130
        if (empty($this->data[ApiInterface::OPEN_API_KEY])) {
131
            throw new SchemaException(ErrorsInterface::CONSOLE_ERRORS[ErrorsInterface::CODE_OPEN_API_KEY],
132
                ErrorsInterface::CODE_OPEN_API_KEY);
133
        }
134
135
        $schemas = $this->data[ApiInterface::API_COMPONENTS][ApiInterface::API_SCHEMAS];
136
        if (empty($schemas[CustomsInterface::CUSTOM_TYPES_ID])
137
            || empty($schemas[CustomsInterface::CUSTOM_TYPES_TYPE])
138
            || empty($schemas[CustomsInterface::CUSTOM_RELATIONSHIPS_DATA_ITEM])) {
139
            throw new SchemaException(ErrorsInterface::CONSOLE_ERRORS[ErrorsInterface::CODE_CUSTOM_TYPES],
140
                ErrorsInterface::CODE_CUSTOM_TYPES);
141
        }
142
143
        if (empty($this->data[ApiInterface::API_INFO])) {
144
            $this->warn(ApiInterface::API_INFO . ': field would be convenient to show users what this API is about');
145
        }
146
    }
147
148
    /**
149
     * Main input point of generator
150
     *
151
     * @throws \Symfony\Component\Yaml\Exception\ParseException
152
     */
153
    private function generateOpenApi()
154
    {
155
        $this->appDir = DirsInterface::APPLICATION_DIR;
156
        $this->controllersDir = DirsInterface::CONTROLLERS_DIR;
157
        $this->entitiesDir = DirsInterface::ENTITIES_DIR;
158
        $this->modulesDir = DirsInterface::MODULES_DIR;
159
        $this->httpDir = DirsInterface::HTTP_DIR;
160
        $this->formRequestDir = DirsInterface::FORM_REQUEST_DIR;
161
        $this->migrationsDir = DirsInterface::MIGRATIONS_DIR;
162
163
        foreach ($this->data[ApiInterface::API_SERVERS] as $server) {
164
            $vars = $server[ApiInterface::API_VARS];
165
            $this->version = $vars[ApiInterface::API_BASE_PATH][ApiInterface::API_DEFAULT];
166
167
            if (env('APP_ENV') === 'dev') { // for test env based on .env
168
                $this->options = [
169
                    ConsoleInterface::OPTION_REGENERATE => 1,
170
                    ConsoleInterface::OPTION_MIGRATIONS => 1,
171
                    ConsoleInterface::OPTION_TESTS      => 1,
172
                ];
173
            } else {
174
                $this->options = $this->options();
175
            }
176
177
            $this->setIncludedTypes();
178
            $this->runGenerator();
179
180
            try {
181
                if ($this->isRollback === false) {
182
                    $this->setGenHistory();
183
                }
184
            } catch (DirectoryException $ex) {
185
                $this->error($ex->getTraceAsString());
186
            }
187
        }
188
    }
189
190
    /**
191
     *  Main generator method - the sequence of methods execution is crucial
192
     *
193
     * @throws \Symfony\Component\Yaml\Exception\ParseException
194
     */
195
    private function runGenerator()
196
    {
197
        if (empty($this->options[ConsoleInterface::OPTION_MERGE]) === false) { // create new or regenerate
198
            $this->setMergedTypes();
199
            $this->isMerge = true;
200
        }
201
202
        $this->generateModule();
203
        $this->generateConfig();
204
205
        $this->generate();
206
    }
207
208
    /**
209
     *  Generates new code or regenerate older with new content
210
     */
211
    private function generate()
212
    {
213
        foreach ($this->types as $objName => $objData) {
214
            if (in_array($objName, $this->customTypes) === false) { // if this is not a custom type generate resources
215
                $excluded = false;
216
                foreach ($this->excludedSubtypes as $type) {
217
                    if (strpos($objName, $type) !== false) {
218
                        $excluded = true;
219
                    }
220
                }
221
                // if the type is among excluded - continue
222
                if ($excluded === true) {
223
                    continue;
224
                }
225
                $this->processObjectData($objName, $objData);
226
            }
227
        }
228
    }
229
230
    /**
231
     * @param string $objName
232
     * @param array $objData
233
     */
234
    private function processObjectData(string $objName, array $objData)
235
    {
236
        foreach ($objData as $k => $v) {
237
            if ($k === ApiInterface::RAML_PROPS) { // process props
238
                $this->setObjectName($objName);
239
                $this->setObjectProps($v);
240
                if (true === $this->isMerge) {
241
                    $this->mergeResources();
242
                } else {
243
                    $this->generateResources();
244
                }
245
            }
246
        }
247
    }
248
249
    private function generateModule()
250
    {
251
        $module = new Module($this);
252
        $module->create();
253
    }
254
255
    private function generateConfig()
256
    {
257
        $module = new Config($this);
258
        $module->create();
259
    }
260
261
    /**
262
     * @throws \SoliDry\Exceptions\DirectoryException
263
     */
264
    public function createDirs()
265
    {
266
        // create modules dir
267
        FileManager::createPath(FileManager::getModulePath($this));
268
        // create config dir
269
        FileManager::createPath($this->formatConfigPath());
270
        // create Controllers dir
271
        FileManager::createPath($this->formatControllersPath());
272
        // create forms dir
273
        FileManager::createPath($this->formatRequestsPath());
274
        // create mapper dir
275
        FileManager::createPath($this->formatEntitiesPath());
276
        // create migrations dir
277
        FileManager::createPath($this->formatMigrationsPath());
278
    }
279
280
    public function formatControllersPath(): string
281
    {
282
        /** @var Command $this */
283
        return FileManager::getModulePath($this, true) . $this->controllersDir;
284
    }
285
286
    public function formatRequestsPath(): string
287
    {
288
        /** @var Command $this */
289
        return FileManager::getModulePath($this, true) . $this->formRequestDir;
290
    }
291
292
    public function formatEntitiesPath(): string
293
    {
294
        /** @var Command $this */
295
        return FileManager::getModulePath($this) . $this->entitiesDir;
296
    }
297
298
    public function formatMigrationsPath(): string
299
    {
300
        /** @var Command $this */
301
        return FileManager::getModulePath($this) . DirsInterface::DATABASE_DIR . PhpInterface::SLASH
302
            . $this->migrationsDir . PhpInterface::SLASH;
303
    }
304
305
    public function formatConfigPath()
306
    {
307
        return FileManager::getModulePath($this) . DirsInterface::MODULE_CONFIG_DIR . PhpInterface::SLASH;
308
    }
309
310
    public function formatGenPath()
311
    {
312
        return DirsInterface::GEN_DIR . PhpInterface::SLASH . date('Y-m-d') . PhpInterface::SLASH;
313
    }
314
315
    public function formatGenPathByDir(): string
316
    {
317
        return DirsInterface::GEN_DIR . PhpInterface::SLASH . $this->genDir . PhpInterface::SLASH;
318
    }
319
320
    public function formatFuncTestsPath()
321
    {
322
        return DirsInterface::TESTS_DIR . PhpInterface::SLASH . DirsInterface::TESTS_FUNC_DIR;
323
    }
324
325
    /**
326
     * @param string $name
327
     */
328
    private function setObjectName(string $name)
329
    {
330
        $this->objectName = $name;
331
    }
332
333
    private function setObjectProps($props)
334
    {
335
        $this->objectProps = $props;
336
    }
337
338
    private function setIncludedTypes()
339
    {
340
        $this->types = $this->data[ApiInterface::API_COMPONENTS][ApiInterface::API_SCHEMAS];
341
        if (empty($this->data[ApiInterface::RAML_KEY_USES]) === false) {
342
            if ($this->isRollback) {
343
                foreach ($this->files as $file) {
344
                    $fileData = $this->parser::parse(file_get_contents($this->formatGenPathByDir() . $file));
345
                    $this->types += $fileData[ApiInterface::API_COMPONENTS][ApiInterface::API_SCHEMAS];
346
                }
347
            } else {
348
                $files = $this->data[ApiInterface::RAML_KEY_USES];
349
                foreach ($files as $file) {
350
                    $this->files[] = $file;
351
                    $fileData = $this->parser::parse(file_get_contents($file));
352
                    $this->types += $fileData[ApiInterface::API_COMPONENTS][ApiInterface::API_SCHEMAS];
353
                }
354
            }
355
        }
356
    }
357
358
    /**
359
     * @throws \SoliDry\Exceptions\DirectoryException
360
     */
361
    private function setGenHistory()
362
    {
363
        if (empty($this->options[ConsoleInterface::OPTION_NO_HISTORY])) {
364
            // create .gen dir to store raml history
365
            FileManager::createPath($this->formatGenPath());
366
            foreach ($this->files as $file) {
367
                $pathInfo = pathinfo($file);
368
                $dest = $this->formatGenPath() . date('His') . PhpInterface::UNDERSCORE
369
                    . $pathInfo['filename'] . PhpInterface::DOT . $pathInfo['extension'];
370
                copy($file, $dest);
371
            }
372
        }
373
    }
374
375
    /**
376
     * Get files to process within rollback
377
     *
378
     * @return array
379
     * @throws \Symfony\Component\Yaml\Exception\ParseException
380
     * @throws DirectoryException
381
     */
382
    protected function getRollbackInputFile(): array
383
    {
384
        $rollBack = $this->option('rollback');
385
        if ($rollBack === ConsoleInterface::MERGE_DEFAULT_VALUE) {
386
            $this->isRollback = true;
387
            return $this->getLastFiles();
388
        }
389
390
        if (is_numeric($rollBack)) {
391
            $dirs = scandir(DirsInterface::GEN_DIR . DIRECTORY_SEPARATOR, SCANDIR_SORT_DESCENDING);
392
            if ($dirs !== false) {
393
                $this->isRollback = true;
394
                $dirs = array_diff($dirs, DirsInterface::EXCLUDED_DIRS);
395
                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

395
                return $this->composeStepFiles($dirs, /** @scrutinizer ignore-type */ $rollBack);
Loading history...
396
            }
397
        }
398
399
        if (strtotime($rollBack) !== false) {
0 ignored issues
show
Bug introduced by
It seems like $rollBack can also be of type string[]; however, parameter $time of strtotime() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

399
        if (strtotime(/** @scrutinizer ignore-type */ $rollBack) !== false) {
Loading history...
400
            $this->isRollback = true;
401
            $dateTime = explode(PhpInterface::SPACE, $rollBack);
0 ignored issues
show
Bug introduced by
It seems like $rollBack can also be of type string[]; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

401
            $dateTime = explode(PhpInterface::SPACE, /** @scrutinizer ignore-type */ $rollBack);
Loading history...
402
403
            return $this->composeTimeFiles($dateTime);
404
        }
405
406
        return [];
407
    }
408
}