Passed
Push — master ( 398928...1438ee )
by Quentin
60:26 queued 54:50
created

ModuleMake::createMigration()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 3.0175

Importance

Changes 0
Metric Value
cc 3
eloc 25
nc 3
nop 1
dl 0
loc 39
ccs 21
cts 24
cp 0.875
crap 3.0175
rs 9.52
c 0
b 0
f 0
1
<?php
2
3
namespace A17\Twill\Commands;
4
5
use Illuminate\Config\Repository as Config;
6
use Illuminate\Filesystem\Filesystem;
7
use Illuminate\Support\Collection;
8
use Illuminate\Support\Composer;
9
use Illuminate\Support\Str;
10
11
class ModuleMake extends Command
12
{
13
    /**
14
     * The name and signature of the console command.
15
     *
16
     * @var string
17
     */
18
    protected $signature = 'twill:make:module {moduleName}
19
        {--B|hasBlocks}
20
        {--T|hasTranslation}
21
        {--S|hasSlug}
22
        {--M|hasMedias}
23
        {--F|hasFiles}
24
        {--P|hasPosition}
25
        {--R|hasRevisions}
26
        {--all}';
27
28
    /**
29
     * The console command description.
30
     *
31
     * @var string
32
     */
33
    protected $description = 'Create a new Twill Module';
34
35
    /**
36
     * @var Filesystem
37
     */
38
    protected $files;
39
40
    /**
41
     * @var Composer
42
     */
43
    protected $composer;
44
45
    /**
46
     * @var string[]
47
     */
48
    protected $modelTraits;
49
50
    /**
51
     * @var string[]
52
     */
53
    protected $repositoryTraits;
54
55
    /**
56
     * @var Config
57
     */
58
    protected $config;
59
60
    /**
61
     * @var bool
62
     */
63
    protected $blockable;
64
65
    /**
66
     * @var bool
67
     */
68
    protected $translatable;
69
70
    /**
71
     * @var bool
72
     */
73
    protected $sluggable;
74
75
    /**
76
     * @var bool
77
     */
78
    protected $mediable;
79
80
    /**
81
     * @var bool
82
     */
83
    protected $fileable;
84
85
    /**
86
     * @var bool
87
     */
88
    protected $sortable;
89
90
    /**
91
     * @var bool
92
     */
93
    protected $revisionable;
94
95
    /**
96
     * @var bool
97
     */
98
    protected $defaultsAnswserToNo;
99
100
    /**
101
     * @param Filesystem $files
102
     * @param Composer $composer
103
     * @param Config $config
104
     */
105 69
    public function __construct(Filesystem $files, Composer $composer, Config $config)
106
    {
107 69
        parent::__construct();
108
109 69
        $this->files = $files;
110 69
        $this->composer = $composer;
111 69
        $this->config = $config;
112
113 69
        $this->blockable = false;
114 69
        $this->translatable = false;
115 69
        $this->sluggable = false;
116 69
        $this->mediable = false;
117 69
        $this->fileable = false;
118 69
        $this->sortable = false;
119 69
        $this->revisionable = false;
120
121 69
        $this->defaultsAnswserToNo = false;
122
123 69
        $this->modelTraits = ['HasBlocks', 'HasTranslation', 'HasSlug', 'HasMedias', 'HasFiles', 'HasRevisions', 'HasPosition'];
124 69
        $this->repositoryTraits = ['HandleBlocks', 'HandleTranslations', 'HandleSlugs', 'HandleMedias', 'HandleFiles', 'HandleRevisions'];
125 69
    }
126
127
    /**
128
     * Executes the console command.
129
     *
130
     * @return mixed
131
     */
132 1
    public function handle()
133
    {
134 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

134
        $moduleName = Str::plural(lcfirst(/** @scrutinizer ignore-type */ $this->argument('moduleName')));
Loading history...
135
136 1
        $enabledOptions = Collection::make($this->options())->only([
137 1
            'hasBlocks',
138
            'hasTranslation',
139
            'hasSlug',
140
            'hasMedias',
141
            'hasFiles',
142
            'hasPosition',
143
            'hasRevisions',
144
        ])->filter(function ($enabled) {
145 1
            return $enabled;
146 1
        });
147
148 1
        if (count($enabledOptions) > 0) {
149 1
            $this->defaultsAnswserToNo = true;
150
        }
151
152 1
        $this->blockable = $this->checkOption('hasBlocks');
153 1
        $this->translatable = $this->checkOption('hasTranslation');
154 1
        $this->sluggable = $this->checkOption('hasSlug');
155 1
        $this->mediable = $this->checkOption('hasMedias');
156 1
        $this->fileable = $this->checkOption('hasFiles');
157 1
        $this->sortable = $this->checkOption('hasPosition');
158 1
        $this->revisionable = $this->checkOption('hasRevisions');
159
160
        $activeTraits = [
161 1
            $this->blockable,
162 1
            $this->translatable,
163 1
            $this->sluggable,
164 1
            $this->mediable,
165 1
            $this->fileable,
166 1
            $this->revisionable,
167 1
            $this->sortable,
168
        ];
169
170 1
        $modelName = Str::studly(Str::singular($moduleName));
171
172 1
        $this->createMigration($moduleName);
173 1
        $this->createModels($modelName, $activeTraits);
174 1
        $this->createRepository($modelName, $activeTraits);
175 1
        $this->createController($moduleName, $modelName);
176 1
        $this->createRequest($modelName);
177 1
        $this->createViews($moduleName);
178
179 1
        $this->info("Add Route::module('{$moduleName}'); to your admin routes file.");
180 1
        $this->info("Setup a new CMS menu item in config/twill-navigation.php:");
181
182 1
        $navTitle = Str::studly($moduleName);
183 1
        $this->info("
184 1
            '{$moduleName}' => [
185 1
                'title' => '{$navTitle}',
186
                'module' => true
187
            ]
188
        ");
189
190 1
        $this->info("Migrate your database.\n");
191
192 1
        $this->info("Enjoy.");
193
194 1
        $this->composer->dumpAutoloads();
195 1
    }
196
197
    /**
198
     * Creates a new module database migration file.
199
     *
200
     * @param string $moduleName
201
     * @return void
202
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
203
     */
204 1
    private function createMigration($moduleName = 'items')
205
    {
206 1
        $table = Str::snake($moduleName);
207 1
        $tableClassName = Str::studly($table);
208
209 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...
210
211 1
        $migrationName = 'create_' . $table . '_tables';
212
213 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

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