Passed
Push — master ( b5f26b...e951dd )
by Arthur
03:06
created

BaseCommand   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 369
Duplicated Lines 0 %

Importance

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

22 Methods

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

376
                return $this->composeStepFiles($dirs, /** @scrutinizer ignore-type */ $rollBack);
Loading history...
377
            }
378
        }
379
380
        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

380
        if (strtotime(/** @scrutinizer ignore-type */ $rollBack) !== false) {
Loading history...
381
            $this->isRollback = true;
382
            $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

382
            $dateTime = explode(PhpInterface::SPACE, /** @scrutinizer ignore-type */ $rollBack);
Loading history...
383
384
            return $this->composeTimeFiles($dateTime);
385
        }
386
387
        return [];
388
    }
389
}