Passed
Push — master ( e951dd...16bd1d )
by Arthur
02:47
created

BaseCommand   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 356
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 51
eloc 159
dl 0
loc 356
rs 7.92
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 11 2
A processObjectData() 0 10 4
A generateOpenApi() 0 33 5
A generateConfig() 0 4 1
A generate() 0 15 6
A createDirs() 0 14 1
A formatConfigPath() 0 3 1
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 5 1
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 \Symfony\Component\Yaml\Exception\ParseException
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 (env('APP_ENV') === 'dev') { // for test env based on .env
136
                $this->options = [
137
                    ConsoleInterface::OPTION_REGENERATE => 1,
138
                    ConsoleInterface::OPTION_MIGRATIONS => 1,
139
                    ConsoleInterface::OPTION_TESTS      => 1,
140
                ];
141
            } else {
142
                $this->options = $this->options();
143
            }
144
145
            $this->setIncludedTypes();
146
            $this->runGenerator();
147
148
            try {
149
                if ($this->isRollback === false) {
150
                    $this->setGenHistory();
151
                }
152
            } catch (DirectoryException $ex) {
153
                $this->error($ex->getTraceAsString());
154
            }
155
        }
156
    }
157
158
    /**
159
     *  Main generator method - the sequence of methods execution is crucial
160
     *
161
     * @throws \Symfony\Component\Yaml\Exception\ParseException
162
     */
163
    private function runGenerator()
164
    {
165
        if (empty($this->options[ConsoleInterface::OPTION_MERGE]) === false) { // create new or regenerate
166
            $this->setMergedTypes();
167
            $this->isMerge = true;
168
        }
169
170
        $this->generateModule();
171
        $this->generateConfig();
172
173
        $this->generate();
174
    }
175
176
    /**
177
     *  Generates new code or regenerate older with new content
178
     */
179
    private function generate()
180
    {
181
        foreach ($this->types as $objName => $objData) {
182
            if (in_array($objName, $this->customTypes) === false) { // if this is not a custom type generate resources
183
                $excluded = false;
184
                foreach ($this->excludedSubtypes as $type) {
185
                    if (strpos($objName, $type) !== false) {
186
                        $excluded = true;
187
                    }
188
                }
189
                // if the type is among excluded - continue
190
                if ($excluded === true) {
191
                    continue;
192
                }
193
                $this->processObjectData($objName, $objData);
194
            }
195
        }
196
    }
197
198
    /**
199
     * @param string $objName
200
     * @param array $objData
201
     */
202
    private function processObjectData(string $objName, array $objData)
203
    {
204
        foreach ($objData as $k => $v) {
205
            if ($k === ApiInterface::RAML_PROPS) { // process props
206
                $this->setObjectName($objName);
207
                $this->setObjectProps($v);
208
                if (true === $this->isMerge) {
209
                    $this->mergeResources();
210
                } else {
211
                    $this->generateResources();
212
                }
213
            }
214
        }
215
    }
216
217
    private function generateModule()
218
    {
219
        $module = new Module($this);
220
        $module->create();
221
    }
222
223
    private function generateConfig()
224
    {
225
        $module = new Config($this);
226
        $module->create();
227
    }
228
229
    /**
230
     * @throws \SoliDry\Exceptions\DirectoryException
231
     */
232
    public function createDirs()
233
    {
234
        // create modules dir
235
        FileManager::createPath(FileManager::getModulePath($this));
236
        // create config dir
237
        FileManager::createPath($this->formatConfigPath());
238
        // create Controllers dir
239
        FileManager::createPath($this->formatControllersPath());
240
        // create forms dir
241
        FileManager::createPath($this->formatRequestsPath());
242
        // create mapper dir
243
        FileManager::createPath($this->formatEntitiesPath());
244
        // create migrations dir
245
        FileManager::createPath($this->formatMigrationsPath());
246
    }
247
248
    public function formatControllersPath() : string
249
    {
250
        /** @var Command $this */
251
        return FileManager::getModulePath($this, true) . $this->controllersDir;
252
    }
253
254
    public function formatRequestsPath() : string
255
    {
256
        /** @var Command $this */
257
        return FileManager::getModulePath($this, true) . $this->formRequestDir;
258
    }
259
260
    public function formatEntitiesPath() : string
261
    {
262
        /** @var Command $this */
263
        return FileManager::getModulePath($this) . $this->entitiesDir;
264
    }
265
266
    public function formatMigrationsPath() : string
267
    {
268
        /** @var Command $this */
269
        return FileManager::getModulePath($this) . DirsInterface::DATABASE_DIR . PhpInterface::SLASH
270
            . $this->migrationsDir . PhpInterface::SLASH;
271
    }
272
273
    public function formatConfigPath()
274
    {
275
        return FileManager::getModulePath($this) . DirsInterface::MODULE_CONFIG_DIR . PhpInterface::SLASH;
276
    }
277
278
    public function formatGenPath()
279
    {
280
        return DirsInterface::GEN_DIR . PhpInterface::SLASH . date('Y-m-d') . PhpInterface::SLASH;
281
    }
282
283
    public function formatGenPathByDir(): string
284
    {
285
        return DirsInterface::GEN_DIR . PhpInterface::SLASH . $this->genDir . PhpInterface::SLASH;
286
    }
287
288
    public function formatFuncTestsPath()
289
    {
290
        return DirsInterface::TESTS_DIR . PhpInterface::SLASH . DirsInterface::TESTS_FUNC_DIR;
291
    }
292
293
    /**
294
     * @param string $name
295
     */
296
    private function setObjectName(string $name)
297
    {
298
        $this->objectName = $name;
299
    }
300
301
    private function setObjectProps($props)
302
    {
303
        $this->objectProps = $props;
304
    }
305
306
    private function setIncludedTypes()
307
    {
308
        $this->types = $this->data[ApiInterface::API_COMPONENTS][ApiInterface::API_SCHEMAS];
309
        if (empty($this->data[ApiInterface::RAML_KEY_USES]) === false) {
310
            if ($this->isRollback) {
311
                foreach ($this->files as $file) {
312
                    $fileData    = Yaml::parse(file_get_contents($this->formatGenPathByDir() . $file));
313
                    $this->types += $fileData[ApiInterface::API_COMPONENTS][ApiInterface::API_SCHEMAS];
314
                }
315
            } else {
316
                $files = $this->data[ApiInterface::RAML_KEY_USES];
317
                foreach ($files as $file) {
318
                    $this->files[] = $file;
319
                    $fileData      = Yaml::parse(file_get_contents($file));
320
                    $this->types   += $fileData[ApiInterface::API_COMPONENTS][ApiInterface::API_SCHEMAS];
321
                }
322
            }
323
        }
324
    }
325
326
    /**
327
     * @throws \SoliDry\Exceptions\DirectoryException
328
     */
329
    private function setGenHistory()
330
    {
331
        if (empty($this->options[ConsoleInterface::OPTION_NO_HISTORY])) {
332
            // create .gen dir to store raml history
333
            FileManager::createPath($this->formatGenPath());
334
            foreach ($this->files as $file) {
335
                $pathInfo = pathinfo($file);
336
                $dest     = $this->formatGenPath() . date('His') . PhpInterface::UNDERSCORE
337
                    . $pathInfo['filename'] . PhpInterface::DOT . $pathInfo['extension'];
338
                copy($file, $dest);
339
            }
340
        }
341
    }
342
343
    /**
344
     * Get files to process within rollback
345
     *
346
     * @return array
347
     * @throws \Symfony\Component\Yaml\Exception\ParseException
348
     * @throws DirectoryException
349
     */
350
    protected function getRollbackInputFile(): array
351
    {
352
        $rollBack = $this->option('rollback');
353
        if ($rollBack === ConsoleInterface::MERGE_DEFAULT_VALUE) {
354
            $this->isRollback = true;
355
            return $this->getLastFiles();
356
        }
357
358
        if (is_numeric($rollBack)) {
359
            $dirs = scandir(DirsInterface::GEN_DIR . DIRECTORY_SEPARATOR, SCANDIR_SORT_DESCENDING);
360
            if ($dirs !== false) {
361
                $this->isRollback = true;
362
                $dirs = array_diff($dirs, DirsInterface::EXCLUDED_DIRS);
363
                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

363
                return $this->composeStepFiles($dirs, /** @scrutinizer ignore-type */ $rollBack);
Loading history...
364
            }
365
        }
366
367
        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

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

369
            $dateTime = explode(PhpInterface::SPACE, /** @scrutinizer ignore-type */ $rollBack);
Loading history...
370
371
            return $this->composeTimeFiles($dateTime);
372
        }
373
374
        return [];
375
    }
376
}