Passed
Pull Request — master (#209)
by
unknown
10:30
created

BuildPageFilesCommand::buildFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 9
c 1
b 0
f 0
dl 0
loc 14
ccs 0
cts 11
cp 0
rs 9.9666
cc 1
nc 1
nop 2
crap 2
1
<?php
2
/**
3
 * Build Page Files Command.
4
 *
5
 * @package App\Console\Commands\BuildPageFiles
6
 *
7
 * @author    Nick Menke <[email protected]>
8
 * @copyright 2018-2020 Nick Menke
9
 *
10
 * @link https://github.com/nlmenke/vertebrae
11
 */
12
13
declare(strict_types=1);
14
15
namespace App\Console\Commands\BuildPageFiles;
16
17
use File;
18
use Illuminate\Console\Command;
19
use Illuminate\Support\Composer;
20
use Illuminate\Support\Str;
21
use Symfony\Component\Console\Input\InputArgument;
22
use Symfony\Component\Console\Input\InputOption;
23
24
/**
25
 * Builds class files required for a new API/page.
26
 *
27
 * This command will generate the classes and other files needed to make a new
28
 * API/page for your application. You can choose whether you're building an API
29
 * or a page - this will tell the generator which classes need to be created.
30
 *
31
 * @since x.x.x introduced
32
 */
33
class BuildPageFilesCommand extends Command
34
{
35
    /**
36
     * The Composer instance.
37
     *
38
     * @var Composer
39
     */
40
    protected $composer;
41
42
    /**
43
     * The default feature type.
44
     *
45
     * @var string
46
     */
47
    protected $defaultType = 'api';
48
49
    /**
50
     * The console command description.
51
     *
52
     * @var string
53
     */
54
    protected $description = 'Build the files needed for a new API/page.';
55
56
    /**
57
     * The console command name.
58
     *
59
     * @var string
60
     */
61
    protected $name = 'build:files';
62
63
    /**
64
     * The selected feature type.
65
     *
66
     * @var string
67
     */
68
    protected $selectedType;
69
70
    /**
71
     * Create a new command instance.
72
     *
73
     * @param Composer $composer
74
     *
75
     * @return void
76
     */
77 33
    public function __construct(Composer $composer)
78
    {
79 33
        parent::__construct();
80
81 33
        $this->composer = $composer;
82
    }
83
84
    /**
85
     * Execute the console command.
86
     *
87
     * @return void
88
     */
89
    public function handle(): void
90
    {
91
        if ($this->laravel->environment() !== 'local') {
92
            $this->error('Files should only be generated in a local environment');
93
            exit;
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...
94
        }
95
96
        if ($this->option('type') === null || !in_array($this->option('type'), ['api', 'page'], true)) {
97
            $this->selectedType = $this->choice('What type of feature will this be?', ['api', 'page'], $this->defaultType);
98
        } else {
99
            $this->selectedType = $this->option('type');
100
        }
101
102
        $name = Str::studly(Str::singular($this->argument('name')));
0 ignored issues
show
Bug introduced by
It seems like $this->argument('name') can also be of type array; however, parameter $value of Illuminate\Support\Str::singular() 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

102
        $name = Str::studly(Str::singular(/** @scrutinizer ignore-type */ $this->argument('name')));
Loading history...
103
104
        $controllerPath = app_path('Http/Controllers');
105
        if ($this->isApi()) {
106
            $controllerPath = $controllerPath . '/Api';
107
            $controllerFile = $name . 'ApiController.php';
108
        } else {
109
            $controllerFile = $name . 'Controller.php';
110
        }
111
112
        $modelPath = app_path('Entities/' . $name);
113
        $modelFile = $name . '.php';
114
115
        $requestPath = app_path('Http/Requests/' . $name);
116
        $createRequestFile = 'Create' . $name . 'Request.php';
117
        $updateRequestFile = 'Update' . $name . 'Request.php';
118
119
        $resourcePath = app_path('Http/Resources/' . $name);
120
        $resourceFile = $name . 'Resource.php';
121
122
        $stylePath = resource_path('assets/sass/pages');
123
        $styleFile = '_' . Str::plural(Str::snake($name, '-')) . '.scss';
124
125
        $languageFile = Str::plural(Str::snake($name, '-')) . '.php';
126
127
        $featureTestPath = base_path('tests/Feature/Controllers');
128
        if ($this->isApi()) {
129
            $featureTestPath = $featureTestPath . '/Api';
130
            $featureTestFile = $name . 'ApiControllerTest.php';
131
        } else {
132
            $featureTestFile = $name . 'ControllerTest.php';
133
        }
134
135
        $unitTestPath = base_path('tests/Unit');
136
        $unitTestFile = $name . 'Test.php';
137
138
        // check for existing model; no need to create if files exist
139
        if (File::exists($modelPath . '/' . $modelFile)) {
140
            $this->error($name . ' already exists!');
141
            exit;
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...
142
        }
143
144
        // create controller and add to Git
145
        $controller = $this->buildFile($name, ($this->isApi() ? 'controller.api' : 'controller'));
146
        $this->saveFile($controllerPath, $controllerFile, $controller);
147
        $this->gitAdd($controllerPath . '/' . $controllerFile);
148
149
        // create model and add to Git
150
        $model = $this->buildFile($name, 'model');
151
        $this->saveFile($modelPath, $modelFile, $model);
152
        $this->gitAdd($modelPath . '/' . $modelFile);
153
154
        // create create request and add to Git
155
        $createRequest = $this->buildFile($name, 'request.create');
156
        $this->saveFile($requestPath, $createRequestFile, $createRequest);
157
        $this->gitAdd($requestPath . '/' . $createRequestFile);
158
159
        // create update request and add to Git
160
        $updateRequest = $this->buildFile($name, 'request.update');
161
        $this->saveFile($requestPath, $updateRequestFile, $updateRequest);
162
        $this->gitAdd($requestPath . '/' . $updateRequestFile);
163
164
        if ($this->isApi()) {
165
            // create API resource and add to Git
166
            $resource = $this->buildFile($name, 'resource');
167
            $this->saveFile($resourcePath, $resourceFile, $resource);
168
            $this->gitAdd($resourcePath . '/' . $resourceFile);
169
        }
170
171
        // create stylesheet and add to Git
172
        $this->saveFile($stylePath, $styleFile, '');
173
        $this->gitAdd($stylePath . '/' . $styleFile);
174
175
        // create languages and add to Git
176
        foreach (File::directories(resource_path('lang')) as $languagePath) {
177
            $language = $this->buildfile(Str::snake($name, '-'), 'lang');
178
            $this->saveFile($languagePath, $languageFile, $language);
179
            $this->gitAdd($languagePath . '/' . $languageFile);
180
        }
181
182
        // create feature test and add to Git
183
        $featureTest = $this->buildFile($name, ($this->isApi() ? 'test.feature.controller.api' : 'test.feature.controller'));
184
        $this->saveFile($featureTestPath, $featureTestFile, $featureTest);
185
        $this->gitAdd($featureTestPath . '/' . $featureTestFile);
186
187
        // create unit test and add to Git
188
        $unitTest = $this->buildFile($name, 'test.unit');
189
        $this->saveFile($unitTestPath, $unitTestFile, $unitTest);
190
        $this->gitAdd($unitTestPath . '/' . $unitTestFile);
191
192
        if ($this->option('migration')) {
193
            // create migration and add to Git
194
            $this->buildMigration($name);
195
            $this->gitAdd('$(git ls-files database/migrations --other --exclude-standard)');
196
        }
197
198
        if ($this->option('seeder')) {
199
            // create seeder and add to Git
200
            $seederPath = database_path('seeders');
201
            $seederFile = Str::plural($name) . 'TableSeeder.php';
202
203
            $seeder = $this->buildFile($name, 'seeder');
204
            $this->saveFile($seederPath, $seederFile, $seeder);
205
            $this->gitAdd($seederPath . '/' . $seederFile);
206
        }
207
208
        if ($this->option('factory')) {
209
            // create factory and add to Git
210
            $factoryPath = database_path('factories/Entities/' . $name);
211
            $factoryFile = $name . 'Factory.php';
212
213
            $factory = $this->buildFile($name, 'factory');
214
            $this->saveFile($factoryPath, $factoryFile, $factory);
215
            $this->gitAdd($factoryPath . '/' . $factoryFile);
216
        }
217
218
        // dump composer autoload so everything gets loaded properly
219
        $this->composer->dumpAutoloads();
220
221
        $this->info('File generation complete!');
222
    }
223
224
    /**
225
     * Get the console command arguments.
226
     *
227
     * @return array
228
     */
229 33
    protected function getArguments(): array
230
    {
231 33
        return [
232 33
            ['name', InputArgument::REQUIRED, 'The name of the API/page being created'],
233 33
        ];
234
    }
235
236
    /**
237
     * Get the console command options.
238
     *
239
     * @return array
240
     */
241 33
    protected function getOptions(): array
242
    {
243 33
        return [
244 33
            ['type', '', InputOption::VALUE_OPTIONAL, 'Specify the feature type: `api` or `page`'],
245 33
            ['migration', 'm', InputOption::VALUE_NONE, 'Generate a migration'],
246 33
            ['seeder', 's', InputOption::VALUE_NONE, 'Generate a seeder'],
247 33
            ['factory', 'f', InputOption::VALUE_NONE, 'Generate a factory'],
248 33
        ];
249
    }
250
251
    /**
252
     * Gets the file stub and fills in the contents.
253
     *
254
     * @param string $name
255
     * @param string $type
256
     *
257
     * @return string
258
     */
259
    private function buildFile(string $name, string $type): string
260
    {
261
        $file = File::get($this->getStub($type));
262
263
        $replacements = [
264
            'LowerDummyRootNamespace' => mb_strtolower($this->laravel->getNamespace()),
265
            'DummyRootNamespace' => $this->laravel->getNamespace(),
266
            'LowerDummy' => mb_strtolower($name),
267
            'Dummy' => $name,
268
            'LowerDummies' => mb_strtolower(Str::plural($name)),
269
            'Dummies' => Str::plural($name),
270
        ];
271
272
        return str_replace(array_keys($replacements), array_values($replacements), $file);
273
    }
274
275
    /**
276
     * Generates a migration file.
277
     *
278
     * @param string $name
279
     *
280
     * @return void
281
     */
282
    private function buildMigration(string $name): void
283
    {
284
        $table = Str::plural(Str::snake($name));
285
        $migration = 'create_' . $table . '_table';
286
287
        $this->call('make:migration', [
288
            'name' => $migration,
289
            '--create' => $table,
290
        ]);
291
    }
292
293
    /**
294
     * Pulls the specified stub file.
295
     *
296
     * @param string $type
297
     *
298
     * @return string
299
     */
300
    private function getStub(string $type): string
301
    {
302
        return __DIR__ . '/stubs/' . $type . '.stub';
303
    }
304
305
    /**
306
     * Adds the file to Git.
307
     *
308
     * @param string $file
309
     *
310
     * @return void
311
     */
312
    private function gitAdd(string $file): void
313
    {
314
        exec('git add ' . $file, $output);
315
316
        $this->output->write($output, true);
317
    }
318
319
    /**
320
     * Checks if the feature should be an API.
321
     *
322
     * @return bool
323
     */
324
    private function isApi(): bool
325
    {
326
        return $this->selectedType === 'api';
327
    }
328
329
    /**
330
     * Saves the generated file.
331
     *
332
     * @param string $path
333
     * @param string $filename
334
     * @param string $file
335
     *
336
     * @return void
337
     */
338
    private function saveFile(string $path, string $filename, string $file): void
339
    {
340
        if (!File::isDirectory($path)) {
341
            File::makeDirectory($path, 493, true, true);
342
        }
343
344
        File::put($path . '/' . $filename, $file);
345
346
        $this->line('<info>Built File:</info> ' . $filename);
347
    }
348
}
349