Passed
Pull Request — master (#532)
by Antonio Carlos
10:20
created

ModuleMake   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 427
Duplicated Lines 0 %

Test Coverage

Coverage 92.86%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 192
c 2
b 0
f 0
dl 0
loc 427
ccs 169
cts 182
cp 0.9286
rs 9.0399
wmc 42

10 Methods

Rating   Name   Duplication   Size   Complexity  
A createMigration() 0 39 3
A handle() 0 63 2
A __construct() 0 20 1
A createController() 0 17 2
A checkOption() 0 17 4
A createRequest() 0 13 2
A createViews() 0 13 3
A renderStubForOption() 0 12 2
F createModels() 0 86 15
B createRepository() 0 25 8

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace A17\Twill\Commands;
4
5
use Illuminate\Config\Repository as Config;
6
use Illuminate\Console\Command;
7
use Illuminate\Filesystem\Filesystem;
8
use Illuminate\Support\Collection;
9
use Illuminate\Support\Composer;
10
use Illuminate\Support\Str;
11
12
class ModuleMake extends Command
13
{
14
    /**
15
     * The name and signature of the console command.
16
     *
17
     * @var string
18
     */
19
    protected $signature = 'twill:module {moduleName}
20
        {--B|hasBlocks}
21
        {--T|hasTranslation}
22
        {--S|hasSlug}
23
        {--M|hasMedias}
24
        {--F|hasFiles}
25
        {--P|hasPosition}
26
        {--R|hasRevisions}
27
        {--all}';
28
29
    /**
30
     * The console command description.
31
     *
32
     * @var string
33
     */
34
    protected $description = 'Create a new Twill Module';
35
36
    /**
37
     * @var Filesystem
38
     */
39
    protected $files;
40
41
    /**
42
     * @var Composer
43
     */
44
    protected $composer;
45
46
    /**
47
     * @var string[]
48
     */
49
    protected $modelTraits;
50
51
    /**
52
     * @var string[]
53
     */
54
    protected $repositoryTraits;
55
56
    /**
57
     * @var Config
58
     */
59
    protected $config;
60
61
    /**
62
     * @param Filesystem $files
63
     * @param Composer $composer
64
     * @param Config $config
65
     */
66 55
    public function __construct(Filesystem $files, Composer $composer, Config $config)
67
    {
68 55
        parent::__construct();
69
70 55
        $this->files = $files;
71 55
        $this->composer = $composer;
72 55
        $this->config = $config;
73
74 55
        $this->blockable = false;
0 ignored issues
show
Bug Best Practice introduced by
The property blockable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
75 55
        $this->translatable = false;
0 ignored issues
show
Bug Best Practice introduced by
The property translatable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
76 55
        $this->sluggable = false;
0 ignored issues
show
Bug Best Practice introduced by
The property sluggable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
77 55
        $this->mediable = false;
0 ignored issues
show
Bug Best Practice introduced by
The property mediable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
78 55
        $this->fileable = false;
0 ignored issues
show
Bug Best Practice introduced by
The property fileable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
79 55
        $this->sortable = false;
0 ignored issues
show
Bug Best Practice introduced by
The property sortable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
80 55
        $this->revisionable = false;
0 ignored issues
show
Bug Best Practice introduced by
The property revisionable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
81
82 55
        $this->defaultsAnswserToNo = false;
0 ignored issues
show
Bug Best Practice introduced by
The property defaultsAnswserToNo does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
83
84 55
        $this->modelTraits = ['HasBlocks', 'HasTranslation', 'HasSlug', 'HasMedias', 'HasFiles', 'HasRevisions', 'HasPosition'];
85 55
        $this->repositoryTraits = ['HandleBlocks', 'HandleTranslations', 'HandleSlugs', 'HandleMedias', 'HandleFiles', 'HandleRevisions'];
86 55
    }
87
88
    /**
89
     * Executes the console command.
90
     *
91
     * @return mixed
92
     */
93 1
    public function handle()
94
    {
95 1
        $moduleName = Str::plural(lcfirst($this->argument('moduleName')));
0 ignored issues
show
Bug introduced by
It seems like $this->argument('moduleName') can also be of type string[]; however, parameter $str of lcfirst() 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

95
        $moduleName = Str::plural(lcfirst(/** @scrutinizer ignore-type */ $this->argument('moduleName')));
Loading history...
96
97 1
        $enabledOptions = Collection::make($this->options())->only([
98 1
            'hasBlocks',
99
            'hasTranslation',
100
            'hasSlug',
101
            'hasMedias',
102
            'hasFiles',
103
            'hasPosition',
104
            'hasRevisions',
105
        ])->filter(function ($enabled) {
106 1
            return $enabled;
107 1
        });
108
109 1
        if (count($enabledOptions) > 0) {
110 1
            $this->defaultsAnswserToNo = true;
0 ignored issues
show
Bug Best Practice introduced by
The property defaultsAnswserToNo does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
111
        }
112
113 1
        $this->blockable = $this->checkOption('hasBlocks');
0 ignored issues
show
Bug Best Practice introduced by
The property blockable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
114 1
        $this->translatable = $this->checkOption('hasTranslation');
0 ignored issues
show
Bug Best Practice introduced by
The property translatable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
115 1
        $this->sluggable = $this->checkOption('hasSlug');
0 ignored issues
show
Bug Best Practice introduced by
The property sluggable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
116 1
        $this->mediable = $this->checkOption('hasMedias');
0 ignored issues
show
Bug Best Practice introduced by
The property mediable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
117 1
        $this->fileable = $this->checkOption('hasFiles');
0 ignored issues
show
Bug Best Practice introduced by
The property fileable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
118 1
        $this->sortable = $this->checkOption('hasPosition');
0 ignored issues
show
Bug Best Practice introduced by
The property sortable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
119 1
        $this->revisionable = $this->checkOption('hasRevisions');
0 ignored issues
show
Bug Best Practice introduced by
The property revisionable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
120
121
        $activeTraits = [
122 1
            $this->blockable,
123 1
            $this->translatable,
124 1
            $this->sluggable,
125 1
            $this->mediable,
126 1
            $this->fileable,
127 1
            $this->revisionable,
128 1
            $this->sortable,
129
        ];
130
131 1
        $modelName = Str::studly(Str::singular($moduleName));
132
133 1
        $this->createMigration($moduleName);
134 1
        $this->createModels($modelName, $activeTraits);
135 1
        $this->createRepository($modelName, $activeTraits);
136 1
        $this->createController($moduleName, $modelName);
137 1
        $this->createRequest($modelName);
138 1
        $this->createViews($moduleName);
139
140 1
        $this->info("Add Route::module('{$moduleName}'); to your admin routes file.");
141 1
        $this->info("Setup a new CMS menu item in config/twill-navigation.php:");
142
143 1
        $navTitle = Str::studly($moduleName);
144 1
        $this->info("
145 1
            '{$moduleName}' => [
146 1
                'title' => '{$navTitle}',
147
                'module' => true
148
            ]
149
        ");
150
151 1
        $this->info("Migrate your database.\n");
152
153 1
        $this->info("Enjoy.");
154
155 1
        $this->composer->dumpAutoloads();
156 1
    }
157
158
    /**
159
     * Creates a new module database migration file.
160
     *
161
     * @param string $moduleName
162
     * @return void
163
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
164
     */
165 1
    private function createMigration($moduleName = 'items')
166
    {
167 1
        $table = Str::snake($moduleName);
168 1
        $tableClassName = Str::studly($table);
169
170 1
        $className = "Create{$tableClassName}Tables";
0 ignored issues
show
Unused Code introduced by
The assignment to $className is dead and can be removed.
Loading history...
171
172 1
        $migrationName = 'create_' . $table . '_tables';
173
174 1
        if (!count(glob(database_path('migrations/*' . $migrationName . '.php')))) {
0 ignored issues
show
Bug introduced by
It seems like glob(database_path('migr...igrationName . '.php')) can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, 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

174
        if (!count(/** @scrutinizer ignore-type */ glob(database_path('migrations/*' . $migrationName . '.php')))) {
Loading history...
175 1
            $migrationPath = $this->laravel->databasePath() . '/migrations';
176
177 1
            $fullPath = $this->laravel['migration.creator']->create($migrationName, $migrationPath);
178
179 1
            $stub = str_replace(
180 1
                ['{{table}}', '{{singularTableName}}', '{{tableClassName}}'],
181 1
                [$table, Str::singular($table), $tableClassName],
182 1
                $this->files->get(__DIR__ . '/stubs/migration.stub')
183
            );
184
185 1
            if ($this->translatable) {
186 1
                $stub = preg_replace('/{{!hasTranslation}}[\s\S]+?{{\/!hasTranslation}}/', '', $stub);
187
            } else {
188
                $stub = str_replace([
189
                    '{{!hasTranslation}}',
190
                    '{{/!hasTranslation}}',
191
                ], '', $stub);
192
            }
193
194 1
            $stub = $this->renderStubForOption($stub, 'hasTranslation', $this->translatable);
195 1
            $stub = $this->renderStubForOption($stub, 'hasSlug', $this->sluggable);
196 1
            $stub = $this->renderStubForOption($stub, 'hasRevisions', $this->revisionable);
197 1
            $stub = $this->renderStubForOption($stub, 'hasPosition', $this->sortable);
198
199 1
            $stub = preg_replace('/\}\);[\s\S]+?Schema::create/', "});\n\n        Schema::create", $stub);
200
201 1
            $this->files->put($fullPath, $stub);
202
203 1
            $this->info("Migration created successfully! Add some fields!");
204
        }
205 1
    }
206
207
    /**
208
     * Creates new model class files for the given model name and traits.
209
     *
210
     * @param string $modelName
211
     * @param array $activeTraits
212
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
213
     */
214 1
    private function createModels($modelName = 'Item', $activeTraits = [])
215
    {
216 1
        $modelClassName = $modelName;
217
218 1
        if (!$this->files->isDirectory(twill_path('Models'))) {
219
            $this->files->makeDirectory(twill_path('Models'));
220
        }
221
222 1
        if ($this->translatable) {
223 1
            if (!$this->files->isDirectory(twill_path('Models/Translations'))) {
224
                $this->files->makeDirectory(twill_path('Models/Translations'));
225
            }
226
227 1
            $modelTranslationClassName = $modelName . 'Translation';
228
229 1
            $stub = str_replace(
230 1
                ['{{modelTranslationClassName}}', '{{modelClassName}}'],
231 1
                [$modelTranslationClassName, $modelClassName],
232 1
                $this->files->get(__DIR__ . '/stubs/model_translation.stub')
233
            );
234
235 1
            $this->files->put(twill_path('Models/Translations/' . $modelTranslationClassName . '.php'), $stub);
236
        }
237
238 1
        if ($this->sluggable) {
239 1
            if (!$this->files->isDirectory(twill_path('Models/Slugs'))) {
240
                $this->files->makeDirectory(twill_path('Models/Slugs'));
241
            }
242
243 1
            $modelSlugClassName = $modelName . 'Slug';
244
245 1
            $stub = str_replace(['{{modelSlugClassName}}', '{{modelName}}'], [$modelSlugClassName, Str::snake($modelName)], $this->files->get(__DIR__ . '/stubs/model_slug.stub'));
246
247 1
            $this->files->put(twill_path('Models/Slugs/' . $modelSlugClassName . '.php'), $stub);
248
        }
249
250 1
        if ($this->revisionable) {
251 1
            if (!$this->files->isDirectory(twill_path('Models/Revisions'))) {
252
                $this->files->makeDirectory(twill_path('Models/Revisions'));
253
            }
254
255 1
            $modelRevisionClassName = $modelName . 'Revision';
256
257 1
            $stub = str_replace(['{{modelRevisionClassName}}', '{{modelName}}'], [$modelRevisionClassName, Str::snake($modelName)], $this->files->get(__DIR__ . '/stubs/model_revision.stub'));
258
259 1
            $this->files->put(twill_path('Models/Revisions/' . $modelRevisionClassName . '.php'), $stub);
260
        }
261
262 1
        $activeModelTraits = [];
263
264 1
        foreach ($activeTraits as $index => $traitIsActive) {
265 1
            if ($traitIsActive) {
266 1
                !isset($this->modelTraits[$index]) ?: $activeModelTraits[] = $this->modelTraits[$index];
267
            }
268
        }
269
270 1
        $activeModelTraitsString = empty($activeModelTraits) ? '' : 'use ' . rtrim(implode(', ', $activeModelTraits), ', ') . ';';
271
272 1
        $activeModelTraitsImports = empty($activeModelTraits) ? '' : "use A17\Twill\Models\Behaviors\\" . implode(";\nuse A17\Twill\Models\Behaviors\\", $activeModelTraits) . ";";
273
274 1
        $activeModelImplements = $this->sortable ? 'implements Sortable' : '';
275
276 1
        if ($this->sortable) {
277 1
            $activeModelTraitsImports .= "\nuse A17\Twill\Models\Behaviors\Sortable;";
278
        }
279
280 1
        $stub = str_replace([
281 1
            '{{modelClassName}}',
282
            '{{modelTraits}}',
283
            '{{modelImports}}',
284
            '{{modelImplements}}',
285
        ], [
286 1
            $modelClassName,
287 1
            $activeModelTraitsString,
288 1
            $activeModelTraitsImports,
289 1
            $activeModelImplements,
290 1
        ], $this->files->get(__DIR__ . '/stubs/model.stub'));
291
292 1
        $stub = $this->renderStubForOption($stub, 'hasTranslation', $this->translatable);
293 1
        $stub = $this->renderStubForOption($stub, 'hasSlug', $this->sluggable);
294 1
        $stub = $this->renderStubForOption($stub, 'hasMedias', $this->mediable);
295 1
        $stub = $this->renderStubForOption($stub, 'hasPosition', $this->sortable);
296
297 1
        $this->files->put(twill_path('Models/' . $modelClassName . '.php'), $stub);
298
299 1
        $this->info("Models created successfully! Fill your fillables!");
300 1
    }
301
302 1
    private function renderStubForOption($stub, $option, $enabled)
303
    {
304 1
        if ($enabled) {
305 1
            $stub = str_replace([
306 1
                '{{' . $option . '}}',
307 1
                '{{/' . $option . '}}',
308 1
            ], '', $stub);
309
        } else {
310
            $stub = preg_replace('/{{' . $option . '}}[\s\S]+?{{\/' . $option . '}}/', '', $stub);
311
        }
312
313 1
        return $stub;
314
    }
315
316
    /**
317
     * Creates new repository class file for the given model name.
318
     *
319
     * @param string $modelName
320
     * @param array $activeTraits
321
     * @return void
322
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
323
     */
324 1
    private function createRepository($modelName = 'Item', $activeTraits = [])
325
    {
326 1
        if (!$this->files->isDirectory(twill_path('Repositories'))) {
327
            $this->files->makeDirectory(twill_path('Repositories'));
328
        }
329
330 1
        $repositoryClassName = $modelName . 'Repository';
331
332 1
        $activeRepositoryTraits = [];
333
334 1
        foreach ($activeTraits as $index => $traitIsActive) {
335 1
            if ($traitIsActive) {
336 1
                !isset($this->repositoryTraits[$index]) ?: $activeRepositoryTraits[] = $this->repositoryTraits[$index];
337
            }
338
        }
339
340 1
        $activeRepositoryTraitsString = empty($activeRepositoryTraits) ? '' : 'use ' . (empty($activeRepositoryTraits) ? "" : rtrim(implode(', ', $activeRepositoryTraits), ', ') . ';');
341
342 1
        $activeRepositoryTraitsImports = empty($activeRepositoryTraits) ? '' : "use A17\Twill\Repositories\Behaviors\\" . implode(";\nuse A17\Twill\Repositories\Behaviors\\", $activeRepositoryTraits) . ";";
343
344 1
        $stub = str_replace(['{{repositoryClassName}}', '{{modelName}}', '{{repositoryTraits}}', '{{repositoryImports}}'], [$repositoryClassName, $modelName, $activeRepositoryTraitsString, $activeRepositoryTraitsImports], $this->files->get(__DIR__ . '/stubs/repository.stub'));
345
346 1
        $this->files->put(twill_path('Repositories/' . $repositoryClassName . '.php'), $stub);
347
348 1
        $this->info("Repository created successfully! Control all the things!");
349 1
    }
350
351
    /**
352
     * Create a new controller class file for the given module name and model name.
353
     *
354
     * @param string $moduleName
355
     * @param string $modelName
356
     * @return void
357
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
358
     */
359 1
    private function createController($moduleName = 'items', $modelName = 'Item')
360
    {
361 1
        if (!$this->files->isDirectory(twill_path('Http/Controllers/Admin'))) {
362
            $this->files->makeDirectory(twill_path('Http/Controllers/Admin'));
363
        }
364
365 1
        $controllerClassName = $modelName . 'Controller';
366
367 1
        $stub = str_replace(
368 1
            ['{{moduleName}}', '{{controllerClassName}}'],
369 1
            [$moduleName, $controllerClassName],
370 1
            $this->files->get(__DIR__ . '/stubs/controller.stub')
371
        );
372
373 1
        $this->files->put(twill_path('Http/Controllers/Admin/' . $controllerClassName . '.php'), $stub);
374
375 1
        $this->info("Controller created successfully! Define your index/browser/form endpoints options!");
376 1
    }
377
378
    /**
379
     * Creates a new request class file for the given model name.
380
     *
381
     * @param string $modelName
382
     * @return void
383
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
384
     */
385 1
    private function createRequest($modelName = 'Item')
386
    {
387 1
        if (!$this->files->isDirectory(twill_path('Http/Requests/Admin'))) {
388
            $this->files->makeDirectory(twill_path('Http/Requests/Admin'), 0755, true);
389
        }
390
391 1
        $requestClassName = $modelName . 'Request';
392
393 1
        $stub = str_replace('{{requestClassName}}', $requestClassName, $this->files->get(__DIR__ . '/stubs/request.stub'));
394
395 1
        $this->files->put(twill_path('Http/Requests/Admin/' . $requestClassName . '.php'), $stub);
396
397 1
        $this->info("Form request created successfully! Add some validation rules!");
398 1
    }
399
400
    /**
401
     * Creates appropriate module Blade view files.
402
     *
403
     * @param string $moduleName
404
     * @return void
405
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
406
     */
407 1
    private function createViews($moduleName = 'items')
408
    {
409 1
        $viewsPath = $this->config->get('view.paths')[0] . '/admin/' . $moduleName;
410
411 1
        if (!$this->files->isDirectory($viewsPath)) {
412 1
            $this->files->makeDirectory($viewsPath, 0755, true);
413
        }
414
415 1
        $formView = $this->translatable ? 'form_translatable' : 'form';
416
417 1
        $this->files->put($viewsPath . '/form.blade.php', $this->files->get(__DIR__ . '/stubs/' . $formView . '.blade.stub'));
418
419 1
        $this->info("Form view created successfully! Include your form fields using @formField directives!");
420 1
    }
421
422 1
    private function checkOption($option)
423
    {
424 1
        if ($this->option($option) || $this->option('all')) {
425 1
            return true;
426
        }
427
428
        $questions = [
429
            'hasBlocks' => 'Do you need to use the block editor on this module?',
430
            'hasTranslation' => 'Do you need to translate content on this module?',
431
            'hasSlug' => 'Do you need to generate slugs on this module?',
432
            'hasMedias' => 'Do you need to attach images on this module?',
433
            'hasFiles' => 'Do you need to attach files on this module?',
434
            'hasPosition' => 'Do you need to manage the position of records on this module?',
435
            'hasRevisions' => 'Do you need to enable revisions on this module?',
436
        ];
437
438
        return 'yes' === $this->choice($questions[$option], ['no', 'yes'], $this->defaultsAnswserToNo ? 0 : 1);
439
    }
440
}
441