ModuleMake   F
last analyzed

Complexity

Total Complexity 91

Size/Duplication

Total Lines 843
Duplicated Lines 0 %

Test Coverage

Coverage 96.57%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 374
dl 0
loc 843
ccs 169
cts 175
cp 0.9657
rs 2
c 5
b 0
f 0
wmc 91

24 Methods

Rating   Name   Duplication   Size   Complexity  
A databasePath() 0 7 3
A checkOption() 0 28 5
B createController() 0 49 6
A createViews() 0 11 2
A createMigration() 0 42 3
F createModels() 0 92 15
A renderStubForOption() 0 12 2
A createCapsuleSeed() 0 11 1
A __construct() 0 21 1
B createRepository() 0 33 9
A createCapsulePath() 0 13 2
A viewPath() 0 9 2
A createSingletonSeed() 0 27 1
A replaceConditionals() 0 20 4
A createCapsuleRoutes() 0 13 1
A replaceVariables() 0 11 3
A removeEmptyLinesWithOnlySpaces() 0 3 1
A createCapsuleNamespace() 0 5 1
A createRequest() 0 17 2
F handle() 0 117 12
A namespace() 0 8 3
A makeDir() 0 14 4
B checkCapsuleDirectory() 0 21 7
A makeTwillDirectory() 0 3 1

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\Filesystem\Filesystem;
7
use Illuminate\Support\Collection;
8
use Illuminate\Support\Composer;
9
use Illuminate\Support\Facades\File;
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:make:module {moduleName}
20
        {--B|hasBlocks}
21
        {--T|hasTranslation}
22
        {--S|hasSlug}
23
        {--M|hasMedias}
24
        {--F|hasFiles}
25
        {--P|hasPosition}
26
        {--R|hasRevisions}
27
        {--N|hasNesting}
28
        {--all}';
29
30
    /**
31
     * The console command description.
32
     *
33
     * @var string
34
     */
35
    protected $description = 'Create a new Twill Module';
36
37
    /**
38
     * @var Filesystem
39
     */
40
    protected $files;
41
42
    /**
43
     * @var Composer
44
     */
45
    protected $composer;
46
47
    /**
48
     * @var string[]
49
     */
50
    protected $modelTraits;
51
52
    /**
53
     * @var string[]
54
     */
55
    protected $repositoryTraits;
56
57
    /**
58
     * @var Config
59
     */
60
    protected $config;
61
62
    /**
63
     * @var bool
64
     */
65
    protected $blockable;
66
67
    /**
68
     * @var bool
69
     */
70
    protected $translatable;
71
72
    /**
73
     * @var bool
74
     */
75
    protected $sluggable;
76
77
    /**
78
     * @var bool
79
     */
80
    protected $mediable;
81
82
    /**
83
     * @var bool
84
     */
85
    protected $fileable;
86
87
    /**
88
     * @var bool
89
     */
90
    protected $sortable;
91
92
    /**
93
     * @var bool
94
     */
95
    protected $revisionable;
96
97
    /**
98
     * @var bool
99
     */
100
    protected $nestable;
101
102
    /**
103
     * @var bool
104
     */
105 69
    protected $defaultsAnswserToNo;
106
107 69
    /**
108
     * @var bool
109 69
     */
110 69
    protected $isCapsule = false;
111 69
112
    /**
113 69
     * @var bool
114 69
     */
115 69
    protected $isSingleton = false;
116 69
117 69
    /**
118 69
     * @var string
119 69
     */
120
    protected $moduleBasePath;
121 69
122
    /**
123 69
     * @var string
124 69
     */
125 69
    protected $capsule;
126
127
    /**
128
     * @param Filesystem $files
129
     * @param Composer $composer
130
     * @param Config $config
131
     */
132 1
    public function __construct(Filesystem $files, Composer $composer, Config $config)
133
    {
134 1
        parent::__construct();
135
136 1
        $this->files = $files;
137 1
        $this->composer = $composer;
138
        $this->config = $config;
139
140
        $this->blockable = false;
141
        $this->translatable = false;
142
        $this->sluggable = false;
143
        $this->mediable = false;
144 1
        $this->fileable = false;
145 1
        $this->sortable = false;
146 1
        $this->revisionable = false;
147
        $this->nestable = false;
148 1
149 1
        $this->defaultsAnswserToNo = false;
150
151
        $this->modelTraits = ['HasBlocks', 'HasTranslation', 'HasSlug', 'HasMedias', 'HasFiles', 'HasRevisions', 'HasPosition', 'HasNesting'];
152 1
        $this->repositoryTraits = ['HandleBlocks', 'HandleTranslations', 'HandleSlugs', 'HandleMedias', 'HandleFiles', 'HandleRevisions', '', 'HandleNesting'];
153 1
    }
154 1
155 1
    protected function checkCapsuleDirectory($dir)
156 1
    {
157 1
        if (file_exists($dir)) {
158 1
            if (!$this->option('force')) {
159
                $answer = $this->choice("Capsule path exists ({$dir}). Erase and overwrite?",
160
                    ['no', 'yes'], $this->defaultsAnswserToNo
161 1
                    ? 0
162 1
                    : 1);
163 1
            }
164 1
165 1
            if ('yes' === ($answer ?? 'no') || $this->option('force')) {
166 1
                File::deleteDirectory($dir);
167 1
168
                if (file_exists($dir)) {
169
                    $this->info("Directory could not be deleted. Aborted.");
170 1
                    die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
171
                }
172 1
            } else {
173 1
                $this->info("Aborted");
174 1
175 1
                die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
176 1
            }
177 1
        }
178
    }
179 1
180 1
    /**
181
     * Executes the console command.
182 1
     *
183 1
     * @return mixed
184 1
     */
185 1
    public function handle()
186
    {
187
        // e.g. newsItems
188
        $moduleName = Str::camel(Str::plural(lcfirst($this->argument('moduleName'))));
0 ignored issues
show
Bug introduced by
It seems like $this->argument('moduleName') can also be of type array and null; however, parameter $string 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

188
        $moduleName = Str::camel(Str::plural(lcfirst(/** @scrutinizer ignore-type */ $this->argument('moduleName'))));
Loading history...
189
190 1
        // e.g. newsItem
191
        $singularModuleName = Str::camel(lcfirst($this->argument('moduleName')));
192 1
193
        // e.g. NewsItems
194 1
        $moduleTitle = Str::studly($moduleName);
195 1
196
        // e.g. NewsItem
197
        $modelName = Str::studly(Str::singular($moduleName));
198
199
        $this->capsule = app('twill.capsules.manager')->makeCapsule(['name' => $moduleName], config("twill.capsules.path"));
0 ignored issues
show
Bug introduced by
The method makeCapsule() does not exist on Illuminate\Contracts\Foundation\Application. ( Ignorable by Annotation )

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

199
        $this->capsule = app('twill.capsules.manager')->/** @scrutinizer ignore-call */ makeCapsule(['name' => $moduleName], config("twill.capsules.path"));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
200
201
        $enabledOptions = Collection::make($this->options())->only([
202
            'hasBlocks',
203
            'hasTranslation',
204 1
            'hasSlug',
205
            'hasMedias',
206 1
            'hasFiles',
207 1
            'hasPosition',
208
            'hasRevisions',
209 1
            'hasNesting',
210
        ])->filter(function ($enabled) {
211 1
            return $enabled;
212
        });
213 1
214 1
        if (count($enabledOptions) > 0) {
215
            $this->defaultsAnswserToNo = true;
216 1
        }
217
218 1
        $this->blockable = $this->checkOption('hasBlocks');
219 1
        $this->translatable = $this->checkOption('hasTranslation');
220 1
        $this->sluggable = $this->checkOption('hasSlug');
221 1
        $this->mediable = $this->checkOption('hasMedias');
222
        $this->fileable = $this->checkOption('hasFiles');
223
        $this->sortable = $this->checkOption('hasPosition');
224 1
        $this->revisionable = $this->checkOption('hasRevisions');
225 1
        $this->nestable = $this->checkOption('hasNesting');
226
227
        if ($this->nestable) {
228
            $this->sortable = true;
229
        }
230
231
        $activeTraits = [
232
            $this->blockable,
233 1
            $this->translatable,
234 1
            $this->sluggable,
235 1
            $this->mediable,
236 1
            $this->fileable,
237
            $this->revisionable,
238 1
            $this->sortable,
239
            $this->nestable,
240 1
        ];
241
242 1
        $this->createCapsuleNamespace($moduleTitle, $modelName);
243
        $this->createCapsulePath($moduleTitle, $modelName);
244 1
245
        $this->createMigration($moduleName);
246
        $this->createModels($modelName, $activeTraits);
247
        $this->createRepository($modelName, $activeTraits);
248
        $this->createController($moduleName, $modelName);
249
        $this->createRequest($modelName);
250
        $this->createViews($moduleName);
251
252
        if ($this->isCapsule) {
253 1
            $this->createCapsuleRoutes($moduleName);
0 ignored issues
show
Unused Code introduced by
The call to A17\Twill\Commands\Modul...::createCapsuleRoutes() has too many arguments starting with $moduleName. ( Ignorable by Annotation )

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

253
            $this->/** @scrutinizer ignore-call */ 
254
                   createCapsuleRoutes($moduleName);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
254
            $this->createCapsuleSeed($moduleName);
255 1
        } elseif ($this->isSingleton) {
256
            $this->createSingletonSeed($modelName);
257 1
            $this->info("\nAdd to routes/admin.php:\n");
258
            $this->info("    Route::singleton('{$singularModuleName}');\n");
259 1
        } else {
260 1
            $this->info("\nAdd to routes/admin.php:\n");
261
            $this->info("    Route::module('{$moduleName}');\n");
262 1
        }
263
264 1
        $navModuleName = $this->isSingleton ? $singularModuleName : $moduleName;
265 1
        $navTitle = $this->isSingleton ? $modelName : $moduleTitle;
266 1
        $navType = $this->isSingleton ? 'singleton' : 'module';
267 1
268
        $this->info("Setup a new CMS menu item in config/twill-navigation.php:\n");
269
        $this->info("    '{$navModuleName}' => [");
270 1
        $this->info("        'title' => '{$navTitle}',");
271
        $this->info("        '{$navType}' => true,");
272
        $this->info("    ],\n");
273 1
274 1
        if ($this->isCapsule) {
275
            $this->info("Setup your new Capsule in config/twill.php:\n");
276 1
            $this->info("    'capsules' => [");
277
            $this->info("        'list' => [");
278 1
            $this->info("            [");
279
            $this->info("                'name' => '{$this->capsule['name']}',");
280 1
            $this->info("                'enabled' => true,");
281
            $this->info("            ],");
282
            $this->info("        ],");
283 1
            $this->info("    ],\n");
284 1
        }
285
286 1
        if ($this->isSingleton) {
287
            $this->info("Migrate your database & seed your singleton module:\n");
288 1
            $this->info("    php artisan migrate\n");
289
            $this->info("    php artisan db:seed {$modelName}Seeder\n");
290 1
        } else {
291
            $this->info("Migrate your database.\n");
292
        }
293 1
294
        $this->info("Enjoy.");
295 1
296 1
        if ($this->nestable && !class_exists('\Kalnoy\Nestedset\NestedSet')) {
297 1
            $this->warn("\nTo support module nesting, you must install the `kalnoy/nestedset` package:");
298
            $this->warn("\n    composer require kalnoy/nestedset\n");
299
        }
300
301 1
        $this->composer->dumpAutoloads();
302
    }
303 1
304
    /**
305 1
     * Creates a new module database migration file.
306
     *
307 1
     * @param string $moduleName
308 1
     * @return void
309
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
310
     */
311 1
    private function createMigration($moduleName = 'items')
312 1
    {
313
        $table = Str::snake($moduleName);
314
        $tableClassName = Str::studly($table);
315
316
        $className = "Create{$tableClassName}Tables";
0 ignored issues
show
Unused Code introduced by
The assignment to $className is dead and can be removed.
Loading history...
317 1
318 1
        $migrationName = 'create_' . $table . '_tables';
319 1
320 1
        if (!count(glob($this->databasePath('migrations/*' . $migrationName . '.php')))) {
321 1
            $migrationPath = $this->databasePath() . '/migrations';
322
323 1
            $this->makeDir($migrationPath);
324 1
325 1
            $fullPath = $this->laravel['migration.creator']->create($migrationName, $migrationPath);
326 1
327
            $stub = str_replace(
328 1
                ['{{table}}', '{{singularTableName}}', '{{tableClassName}}'],
329
                [$table, Str::singular($table), $tableClassName],
330 1
                $this->files->get(__DIR__ . '/stubs/migration.stub')
331 1
            );
332
333 1
            if ($this->translatable) {
334
                $stub = preg_replace('/{{!hasTranslation}}[\s\S]+?{{\/!hasTranslation}}/', '', $stub);
335 1
            } else {
336 1
                $stub = str_replace([
337 1
                    '{{!hasTranslation}}',
338 1
                    '{{/!hasTranslation}}',
339 1
                ], '', $stub);
340
            }
341
342
            $stub = $this->renderStubForOption($stub, 'hasTranslation', $this->translatable);
343
            $stub = $this->renderStubForOption($stub, 'hasSlug', $this->sluggable);
344 1
            $stub = $this->renderStubForOption($stub, 'hasRevisions', $this->revisionable);
345
            $stub = $this->renderStubForOption($stub, 'hasPosition', $this->sortable);
346
            $stub = $this->renderStubForOption($stub, 'hasNesting', $this->nestable);
347
348
            $stub = preg_replace('/\}\);[\s\S]+?Schema::create/', "});\n\n        Schema::create", $stub);
349
350
            $this->files->put($fullPath, $stub);
351
352
            $this->info("Migration created successfully! Add some fields!");
353
        }
354
    }
355 1
356
    /**
357 1
     * Creates new model class files for the given model name and traits.
358
     *
359 1
     * @param string $modelName
360
     * @param array $activeTraits
361 1
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
362
     */
363 1
    private function createModels($modelName = 'Item', $activeTraits = [])
364 1
    {
365 1
        $modelClassName = $this->namespace('models', 'Models', $modelName);
366
367
        $modelsDir = $this->isCapsule ? $this->capsule['models_dir'] : 'Models';
368
369 1
        $this->makeTwillDirectory($modelsDir);
370
371 1
        if ($this->translatable) {
372
            $this->makeTwillDirectory($baseDir = $this->isCapsule ? $modelsDir : "{$modelsDir}/Translations");
373 1
374
            $modelTranslationClassName = $modelName . 'Translation';
375 1
376
            $stub = str_replace(
377 1
                ['{{modelTranslationClassName}}', '{{modelClassWithNamespace}}', '{{modelClassName}}', '{{namespace}}', '{{baseTranslationModel}}'],
378 1
                [$modelTranslationClassName, $modelClassName, $modelName, $this->namespace('models', 'Models\Translations'), config('twill.base_translation_model')],
379
                $this->files->get(__DIR__ . '/stubs/model_translation.stub')
380
            );
381
382
            twill_put_stub(twill_path("{$baseDir}/" . $modelTranslationClassName . '.php'), $stub);
383
        }
384
385
        if ($this->sluggable) {
386
            $this->makeTwillDirectory($baseDir = $this->isCapsule ? $modelsDir : "{$modelsDir}/Slugs");
387
388 1
            $modelSlugClassName = $modelName . 'Slug';
389
390 1
            $stub = str_replace(
391
                ['{{modelSlugClassName}}', '{{modelClassWithNamespace}}', '{{modelName}}', '{{namespace}}', '{{baseSlugModel}}'],
392 1
                [$modelSlugClassName, $modelClassName, Str::snake($modelName), $this->namespace('models', 'Models\Slugs'), config('twill.base_slug_model')],
393
                $this->files->get(__DIR__ . '/stubs/model_slug.stub')
394 1
            );
395 1
396 1
            twill_put_stub(twill_path("{$baseDir}/" . $modelSlugClassName . '.php'), $stub);
397 1
        }
398
399
        if ($this->revisionable) {
400 1
            $this->makeTwillDirectory($baseDir = $this->isCapsule ? $modelsDir : "{$modelsDir}/Revisions");
401
402 1
            $modelRevisionClassName = $modelName . 'Revision';
403 1
404
            $stub = str_replace(
405
                ['{{modelRevisionClassName}}', '{{modelClassWithNamespace}}', '{{modelName}}', '{{namespace}}', '{{baseRevisionModel}}'],
406
                [$modelRevisionClassName, $modelClassName, Str::snake($modelName), $this->namespace('models', 'Models\Revisions'), config('twill.base_revision_model')],
407
                $this->files->get(__DIR__ . '/stubs/model_revision.stub')
408
            );
409
410
            twill_put_stub(twill_path("{$baseDir}/" . $modelRevisionClassName . '.php'), $stub);
411
        }
412 1
413
        $activeModelTraits = [];
414 1
415
        foreach ($activeTraits as $index => $traitIsActive) {
416 1
            if ($traitIsActive) {
417
                !isset($this->modelTraits[$index]) ?: $activeModelTraits[] = $this->modelTraits[$index];
418 1
            }
419
        }
420 1
421
        $activeModelTraitsString = empty($activeModelTraits) ? '' : 'use ' . rtrim(implode(', ', $activeModelTraits), ', ') . ';';
422 1
423 1
        $activeModelTraitsImports = empty($activeModelTraits) ? '' : "use A17\Twill\Models\Behaviors\\" . implode(";\nuse A17\Twill\Models\Behaviors\\", $activeModelTraits) . ";";
424
425
        $activeModelImplements = $this->sortable ? 'implements Sortable' : '';
426
427
        if ($this->sortable) {
428
            $activeModelTraitsImports .= "\nuse A17\Twill\Models\Behaviors\Sortable;";
429
        }
430
431
        $stub = str_replace([
432 1
            '{{modelClassName}}',
433
            '{{modelTraits}}',
434 1
            '{{modelImports}}',
435
            '{{modelImplements}}',
436 1
            '{{namespace}}',
437
            '{{baseModel}}',
438 1
        ], [
439
            $modelName,
440 1
            $activeModelTraitsString,
441
            $activeModelTraitsImports,
442 1
            $activeModelImplements,
443 1
            $this->namespace('models', 'Models'),
444
            config('twill.base_model'),
445 1
        ], $this->files->get(__DIR__ . '/stubs/model.stub'));
446
447 1
        $stub = $this->renderStubForOption($stub, 'hasTranslation', $this->translatable);
448 1
        $stub = $this->renderStubForOption($stub, 'hasSlug', $this->sluggable);
449
        $stub = $this->renderStubForOption($stub, 'hasMedias', $this->mediable);
450
        $stub = $this->renderStubForOption($stub, 'hasPosition', $this->sortable);
451
452
        twill_put_stub(twill_path("{$modelsDir}/" . $modelName . '.php'), $stub);
453
454
        $this->info("Models created successfully! Fill your fillables!");
455
    }
456
457
    private function renderStubForOption($stub, $option, $enabled)
458
    {
459
        if ($enabled) {
460
            $stub = str_replace([
461
                '{{' . $option . '}}',
462
                '{{/' . $option . '}}',
463
            ], '', $stub);
464
        } else {
465
            $stub = preg_replace('/{{' . $option . '}}[\s\S]+?{{\/' . $option . '}}/', '', $stub);
466
        }
467
468
        return $stub;
469
    }
470
471
    /**
472
     * Creates new repository class file for the given model name.
473
     *
474
     * @param string $modelName
475
     * @param array $activeTraits
476
     * @return void
477
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
478
     */
479
    private function createRepository($modelName = 'Item', $activeTraits = [])
480
    {
481
        $modelsDir = $this->isCapsule ? $this->capsule['repositories_dir'] : 'Repositories';
482
483
        $modelClass = $this->isCapsule ? $this->capsule['model'] : config('twill.namespace') . "\Models\\{$this->capsule['singular']}";
484
485
        $this->makeTwillDirectory($modelsDir);
486
487
        $repositoryClassName = $modelName . 'Repository';
488
489
        $activeRepositoryTraits = [];
490
491
        foreach ($activeTraits as $index => $traitIsActive) {
492
            if ($traitIsActive) {
493
                !isset($this->repositoryTraits[$index]) ?: $activeRepositoryTraits[] = $this->repositoryTraits[$index];
494
            }
495
        }
496
497
        $activeRepositoryTraits = array_filter($activeRepositoryTraits);
498
499
        $activeRepositoryTraitsString = empty($activeRepositoryTraits) ? '' : 'use ' . (empty($activeRepositoryTraits) ? "" : rtrim(implode(', ', $activeRepositoryTraits), ', ') . ';');
500
501
        $activeRepositoryTraitsImports = empty($activeRepositoryTraits) ? '' : "use A17\Twill\Repositories\Behaviors\\" . implode(";\nuse A17\Twill\Repositories\Behaviors\\", $activeRepositoryTraits) . ";";
502
503
        $stub = str_replace(
504
            ['{{repositoryClassName}}', '{{modelName}}', '{{repositoryTraits}}', '{{repositoryImports}}', '{{namespace}}', '{{modelClass}}', '{{baseRepository}}'],
505
            [$repositoryClassName, $modelName, $activeRepositoryTraitsString, $activeRepositoryTraitsImports, $this->namespace('repositories', 'Repositories'), $modelClass, config('twill.base_repository')],
506
            $this->files->get(__DIR__ . '/stubs/repository.stub')
507
        );
508
509
        twill_put_stub(twill_path("{$modelsDir}/" . $repositoryClassName . '.php'), $stub);
510
511
        $this->info("Repository created successfully! Control all the things!");
512
    }
513
514
    /**
515
     * Create a new controller class file for the given module name and model name.
516
     *
517
     * @param string $moduleName
518
     * @param string $modelName
519
     * @return void
520
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
521
     */
522
    private function createController($moduleName = 'items', $modelName = 'Item')
523
    {
524
        $controllerClassName = $modelName . 'Controller';
525
526
        $dir = $this->isCapsule ? $this->capsule['controllers_dir'] : 'Http/Controllers/Admin';
527
528
        if ($this->isSingleton) {
529
            $baseController = config('twill.base_singleton_controller');
530
        } elseif ($this->nestable) {
531
            $baseController = config('twill.base_nested_controller');
532
        } else {
533
            $baseController = config('twill.base_controller');
534
        }
535
536
        $this->makeTwillDirectory($dir);
537
538
        $stub = str_replace(
539
            ['{{moduleName}}', '{{controllerClassName}}', '{{namespace}}', '{{baseController}}'],
540
            [$moduleName, $controllerClassName, $this->namespace('controllers', 'Http\Controllers\Admin'), $baseController],
541
            $this->files->get(__DIR__ . '/stubs/controller.stub')
542
        );
543
544
        $permalinkOption = '';
545
        $reorderOption = '';
546
547
        if (!$this->sluggable) {
548
            $permalinkOption = "'permalink' => false,";
549
        }
550
551
        if ($this->nestable) {
552
            $reorderOption = "'reorder' => true,";
553
554
            $stub = str_replace(['{{hasNesting}}', '{{/hasNesting}}'], '', $stub);
555
        } else {
556
            $stub = preg_replace('/{{hasNesting}}[\s\S]+?{{\/hasNesting}}/', '', $stub);
557
        }
558
559
        $stub = str_replace(
560
            ['{{permalinkOption}}', '{{reorderOption}}'],
561
            [$permalinkOption, $reorderOption],
562
            $stub
563
        );
564
565
        // Remove lines including only whitespace, leave true empty lines untouched
566
        $stub = preg_replace('/^[\s]+\n/m', '', $stub);
567
568
        twill_put_stub(twill_path("{$dir}/" . $controllerClassName . '.php'), $stub);
569
570
        $this->info("Controller created successfully! Define your index/browser/form endpoints options!");
571
    }
572
573
    /**
574
     * Creates a new request class file for the given model name.
575
     *
576
     * @param string $modelName
577
     * @return void
578
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
579
     */
580
    private function createRequest($modelName = 'Item')
581
    {
582
        $dir = $this->isCapsule ? $this->capsule['requests_dir'] : 'Http/Requests/Admin';
583
584
        $this->makeTwillDirectory($dir);
585
586
        $requestClassName = $modelName . 'Request';
587
588
        $stub = str_replace(
589
            ['{{requestClassName}}', '{{namespace}}', '{{baseRequest}}'],
590
            [$requestClassName, $this->namespace('requests', 'Http\Requests\Admin'), config('twill.base_request')],
591
            $this->files->get(__DIR__ . '/stubs/request.stub')
592
        );
593
594
        twill_put_stub(twill_path("{$dir}/" . $requestClassName . '.php'), $stub);
595
596
        $this->info("Form request created successfully! Add some validation rules!");
597
    }
598
599
    /**
600
     * Creates appropriate module Blade view files.
601
     *
602
     * @param string $moduleName
603
     * @return void
604
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
605
     */
606
    private function createViews($moduleName = 'items')
607
    {
608
        $viewsPath = $this->viewPath($moduleName);
609
610
        $this->makeTwillDirectory($viewsPath);
611
612
        $formView = $this->translatable ? 'form_translatable' : 'form';
613
614
        twill_put_stub($viewsPath . '/form.blade.php', $this->files->get(__DIR__ . '/stubs/' . $formView . '.blade.stub'));
615
616
        $this->info("Form view created successfully! Include your form fields using @formField directives!");
617
    }
618
619
    /**
620
     * Creates a basic routes file for the Capsule.
621
     *
622
     * @param string $moduleName
623
     * @return void
624
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
625
     */
626
    public function createCapsuleRoutes()
627
    {
628
        $this->makeDir($this->capsule['routes_file']);
629
630
        $contents = str_replace(
631
            '{{moduleName}}',
632
            $this->capsule['module'],
633
            $this->files->get(__DIR__ . '/stubs/routes_admin.stub')
634
        );
635
636
        twill_put_stub($this->capsule['routes_file'], $contents);
637
638
        $this->info("Routes file created successfully!");
639
    }
640
641
    /**
642
     * Creates a new capsule database seed file.
643
     *
644
     * @param string $moduleName
645
     * @return void
646
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
647
     */
648
    private function createCapsuleSeed($moduleName = 'items')
649
    {
650
        $this->makeTwillDirectory($this->capsule['seeds_psr4_path']);
651
652
        $stub = $this->files->get(__DIR__ . '/stubs/database_seeder_capsule.stub');
653
654
        $stub = str_replace('{moduleName}', $this->capsule['plural'], $stub);
655
656
        $this->files->put("{$this->capsule['seeds_psr4_path']}/DatabaseSeeder.php", $stub);
657
658
        $this->info("Seed created successfully!");
659
    }
660
661
    /**
662
     * Creates a new singleton module database seed file.
663
     *
664
     * @param string $modelName
665
     * @return void
666
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
667
     */
668
    private function createSingletonSeed($modelName = 'Item')
669
    {
670
        $repositoryName = $modelName . 'Repository';
671
        $seederName = $modelName . 'Seeder';
672
673
        $dir = $this->databasePath('seeders');
674
675
        $this->makeTwillDirectory($dir);
676
677
        $stub = $this->files->get(__DIR__ . '/stubs/database_seeder_singleton.stub');
678
679
        $stub = $this->replaceVariables([
680
            'seederClassName' => $seederName,
681
            'modelClassName' => $modelName,
682
            'repositoryClassName' => $repositoryName,
683
        ], $stub);
684
685
        $stub = $this->replaceConditionals([
686
            'hasTranslations' => $this->translatable,
687
            '!hasTranslations' => !$this->translatable,
688
        ], $stub);
689
690
        $stub = $this->removeEmptyLinesWithOnlySpaces($stub);
691
692
        $this->files->put("{$dir}/{$seederName}.php", $stub);
693
694
        $this->info("Seed created successfully!");
695
    }
696
697
    private function checkOption($option)
698
    {
699
        if (!$this->hasOption($option)) {
700
            return false;
701
        }
702
703
        if ($this->option($option) || $this->option('all')) {
704
            return true;
705
        }
706
707
        $questions = [
708
            'hasBlocks' => 'Do you need to use the block editor on this module?',
709
            'hasTranslation' => 'Do you need to translate content on this module?',
710
            'hasSlug' => 'Do you need to generate slugs on this module?',
711
            'hasMedias' => 'Do you need to attach images on this module?',
712
            'hasFiles' => 'Do you need to attach files on this module?',
713
            'hasPosition' => 'Do you need to manage the position of records on this module?',
714
            'hasRevisions' => 'Do you need to enable revisions on this module?',
715
            'hasNesting' => 'Do you need to enable nesting on this module?',
716
        ];
717
718
        $defaultAnswers = [
719
            'hasNesting' => 0,
720
        ];
721
722
        $currentDefaultAnswer = $this->defaultsAnswserToNo ? 0 : ($defaultAnswers[$option] ?? 1);
723
724
        return 'yes' === $this->choice($questions[$option], ['no', 'yes'], $currentDefaultAnswer);
725
    }
726
727
    public function createCapsulePath($moduleName, $modelName)
728
    {
729
        if (!$this->isCapsule) {
730
            $this->moduleBasePath = base_path();
731
732
            return;
733
        }
734
735
        $this->checkCapsuleDirectory(
736
            $this->moduleBasePath = config('twill.capsules.path') . "/{$moduleName}"
737
        );
738
739
        $this->makeDir($this->moduleBasePath);
740
    }
741
742
    public function createCapsuleNamespace($module, $model)
743
    {
744
        $base = config('twill.capsules.namespace');
745
746
        $this->capsuleNamespace = "{$base}\\{$module}";
0 ignored issues
show
Bug Best Practice introduced by
The property capsuleNamespace does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
747
    }
748
749
    public function databasePath($path = '')
750
    {
751
        if (!$this->isCapsule) {
752
            return database_path($path);
753
        }
754
755
        return "{$this->moduleBasePath}/database" . (filled($path) ? "/{$path}" : '');
756
    }
757
758
    public function makeDir($dir)
759
    {
760
        $info = pathinfo($dir);
761
762
        $dir = isset($info['extension']) ? $info['dirname'] : $dir;
763
764
        if (!is_dir($dir)) {
765
            mkdir($dir, 0755, true);
766
        }
767
768
        if (!is_dir($dir)) {
769
            $this->info("It wasn't possible to create capsule directory {$dir}");
770
771
            die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
772
        }
773
    }
774
775
    public function makeTwillDirectory($path)
776
    {
777
        make_twill_directory($path);
778
    }
779
780
    public function namespace ($type, $suffix, $class = null) {
781
        $class = (filled($class) ? "\\$class" : '');
782
783
        if (!$this->isCapsule) {
784
            return "App\\{$suffix}{$class}";
785
        }
786
787
        return $this->capsule[$type] . $class;
788
    }
789
790
    public function viewPath($moduleName)
791
    {
792
        if (!$this->isCapsule) {
793
            return $viewsPath = $this->config->get('view.paths')[0] . '/admin/' . $moduleName;
0 ignored issues
show
Unused Code introduced by
The assignment to $viewsPath is dead and can be removed.
Loading history...
794
        }
795
796
        $this->makeDir($dir = "{$this->moduleBasePath}/resources/views/admin");
797
798
        return $dir;
799
    }
800
801
    /**
802
     * @param array $variables
803
     * @param string $stub
804
     * @param array|null $delimiters
805
     * @return string
806
     */
807
    public function replaceVariables($variables, $stub, $delimiters = null)
808
    {
809
        $delimiters = $delimiters ?: ['{{', '}}'];
810
811
        foreach ($variables as $key => $value) {
812
            $key = "{$delimiters[0]}{$key}{$delimiters[1]}";
813
814
            $stub = str_replace($key, $value, $stub);
815
        }
816
817
        return $stub;
818
    }
819
820
    /**
821
     * @param array $variables
822
     * @param string $stub
823
     * @param array|null $delimiters
824
     * @return string
825
     */
826
    public function replaceConditionals($conditionals, $stub, $delimiters = null)
827
    {
828
        $delimiters = $delimiters ?: ['{{', '}}'];
829
830
        foreach ($conditionals as $key => $value) {
831
            $start = "{$delimiters[0]}{$key}{$delimiters[1]}";
832
            $end = "{$delimiters[0]}\/{$key}{$delimiters[1]}";
833
834
            if ((bool)$value) {
835
                // replace delimiters only
836
                $stub = preg_replace("/$start/", '', $stub);
837
                $stub = preg_replace("/$end/", '', $stub);
838
            } else {
839
                // replace delimiters and everything between
840
                $anything = '[\s\S]+?';
841
                $stub = preg_replace("/{$start}{$anything}{$end}/", '', $stub);
842
            }
843
        }
844
845
        return $stub;
846
    }
847
848
    /**
849
     * @param string $stub
850
     * @return string
851
     */
852
    public function removeEmptyLinesWithOnlySpaces($stub)
853
    {
854
        return preg_replace('/^ +\n/m', '', $stub);
855
    }
856
}
857