Completed
Pull Request — master (#27)
by Nick
07:58 queued 01:08
created

BuildPageFilesCommand::isApi()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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