ResourceMakeCommand   B
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 465
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6
Metric Value
wmc 39
lcom 1
cbo 6
dl 0
loc 465
rs 8.2857

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A handle() 0 14 1
A createModelFactory() 0 23 1
A buildFakerAttributes() 0 18 2
A createModel() 0 19 2
B createMigration() 0 24 3
A createController() 0 21 2
A appendRoutes() 0 21 1
A buildMigration() 0 19 1
A buildModel() 0 14 1
A convertModelToTableName() 0 4 1
A buildMigrationFilename() 0 6 1
A replaceClassName() 0 4 1
A addMigrationAttributes() 0 7 1
A parseAttributesFromInputString() 0 15 3
B convertArrayToString() 0 25 3
A addModelAttributes() 0 12 1
A buildTableColumns() 0 18 2
A getFieldTypeFromProperties() 0 15 2
A typeCanDefineSize() 0 4 2
A extractFieldLengthValue() 0 10 3
A extractAttributePropertiesToApply() 0 4 1
A buildSchemaColumn() 0 11 2
A modelName() 0 4 1
1
<?php
2
3
namespace DrawMyAttention\ResourceGenerator\Commands;
4
5
use Illuminate\Support\Str;
6
use Illuminate\Console\Command;
7
use Illuminate\Support\Composer;
8
use Illuminate\Filesystem\Filesystem;
9
10
class ResourceMakeCommand extends Command
11
{
12
    /**
13
     * The name and signature of the console command.
14
     *
15
     * @var string
16
     */
17
    protected $signature = 'make:resource {name : The model name} {attributes?}';
18
19
    /**
20
     * The console command description.
21
     *
22
     * @var string
23
     */
24
    protected $description = 'Create a new model, migration, controller and add routes';
25
26
    /**
27
     * The filesystem instance.
28
     *
29
     * @var \Illuminate\Filesystem\Filesystem
30
     */
31
    private $files;
32
33
    /**
34
     * @var Composer
35
     */
36
    private $composer;
37
38
    /**
39
     * @var array The data types that can be created in a migration.
40
     */
41
    private $dataTypes = [
42
        'string', 'integer', 'boolean', 'bigIncrements', 'bigInteger',
43
        'binary', 'boolean', 'char', 'date', 'dateTime', 'float', 'increments',
44
        'json', 'jsonb', 'longText', 'mediumInteger', 'mediumText', 'nullableTimestamps',
45
        'smallInteger', 'tinyInteger', 'softDeletes', 'text', 'time', 'timestamp',
46
        'timestamps', 'rememberToken',
47
    ];
48
49
    private $fakerMethods = [
50
        'string' => ['method' => 'words', 'parameters' => '2, true'],
51
        'integer' => ['method' => 'randomNumber', 'parameters' => ''],
52
    ];
53
54
    /**
55
     * @var array $columnProperties Properties that can be applied to a table column.
56
     */
57
    private $columnProperties = [
58
        'unsigned', 'index', 'nullable'
59
    ];
60
61
    /**
62
     * Create a new command instance.
63
     *
64
     * @param Filesystem $files
65
     * @param Composer $composer
66
     */
67
    public function __construct(Filesystem $files, Composer $composer)
68
    {
69
        parent::__construct();
70
71
        $this->files = $files;
72
73
        $this->composer = $composer;
74
    }
75
76
    /**
77
     * Execute the console command.
78
     *
79
     * @return mixed
80
     */
81
    public function handle()
82
    {
83
        $name = trim($this->input->getArgument('name'));
84
85
        $this->createModel($name);
86
87
        $this->createMigration($name);
88
89
        $this->createController($name);
90
91
        $this->appendRoutes($name);
92
93
        $this->createModelFactory($name);
94
    }
95
96
    private function createModelFactory($name)
97
    {
98
        $model = $this->modelName($name);
99
100
        $stub = $this->files->get(__DIR__ . '/../Stubs/factory.stub');
101
102
        $stub = str_replace('CLASSNAME', $model, $stub);
103
104
        $class = 'App\\' . $model;
105
        $model = new $class;
106
107
        $stub = str_replace(
108
            'ATTRIBUTES',
109
            $this->buildFakerAttributes($model->migrationAttributes()),
110
            $stub
111
        );
112
113
        $this->files->append(database_path('factories/ModelFactory.php'), $stub);
114
115
        $this->info('Created model factory');
116
117
        return true;
118
    }
119
120
    public function buildFakerAttributes($attributes)
121
    {
122
        $faker = '';
123
124
        foreach ($attributes as $attribute) {
125
126
            $formatter =
127
                $this->fakerMethods[$this->getFieldTypeFromProperties($attribute['properties'])];
128
129
            $method = $formatter['method'];
130
            $parameters = $formatter['parameters'];
131
132
            $faker .= "'".$attribute['name']."' => \$faker->".$method."(".$parameters.")," . PHP_EOL . '        ';
133
134
        }
135
136
        return rtrim($faker);
137
    }
138
139
    /**
140
     * Create and store a new Model to the filesystem.
141
     *
142
     * @param string $name
143
     * @return bool
144
     */
145
    private function createModel($name)
146
    {
147
        $modelName = $this->modelName($name);
148
149
        $filename = $modelName . '.php';
150
151
        if ($this->files->exists(app_path($filename))) {
152
            $this->error('Model already exists!');
153
            return false;
154
        }
155
156
        $model = $this->buildModel($name);
157
158
        $this->files->put(app_path('/' . $filename), $model);
159
160
        $this->info($modelName . ' Model created');
161
162
        return true;
163
    }
164
165
    private function createMigration($name)
166
    {
167
        $filename = $this->buildMigrationFilename($name);
168
169
        if ($this->files->exists(database_path($filename))) {
170
            $this->error('Migration already exists!');
171
            return false;
172
        }
173
174
        $migration = $this->buildMigration($name);
175
176
        $this->files->put(
177
            database_path('/migrations/' . $filename),
178
            $migration
179
        );
180
181
        if (env('APP_ENV') != 'testing') {
182
            $this->composer->dumpAutoloads();
183
        }
184
185
        $this->info('Created migration ' . $filename);
186
187
        return true;
188
    }
189
190
    private function createController($modelName)
191
    {
192
        $filename = ucfirst($modelName) . 'Controller.php';
193
194
        if ($this->files->exists(app_path('Http/' . $filename))) {
195
            $this->error('Controller already exists!');
196
            return false;
197
        }
198
199
        $stub = $this->files->get(__DIR__ . '/../Stubs/controller.stub');
200
201
        $stub = str_replace('MyModelClass', ucfirst($modelName), $stub);
202
        $stub = str_replace('myModelInstance', Str::camel($modelName), $stub);
203
        $stub = str_replace('template', strtolower($modelName), $stub);
204
205
        $this->files->put(app_path('Http/Controllers/' . $filename), $stub);
206
207
        $this->info('Created controller ' . $filename);
208
209
        return true;
210
    }
211
212
    private function appendRoutes($modelName)
213
    {
214
        $modelTitle = ucfirst($modelName);
215
216
        $modelName = strtolower($modelName);
217
218
        $newRoutes = $this->files->get(__DIR__ . '/../Stubs/routes.stub');
219
220
        $newRoutes = str_replace('|MODEL_TITLE|', $modelTitle, $newRoutes);
221
222
        $newRoutes = str_replace('|MODEL_NAME|', $modelName, $newRoutes);
223
224
        $newRoutes = str_replace('|CONTROLLER_NAME|', $modelTitle . 'Controller', $newRoutes);
225
226
        $this->files->append(
227
            app_path('Http/routes.php'),
228
            $newRoutes
229
        );
230
231
        $this->info('Added routes for ' . $modelTitle);
232
    }
233
234
    protected function buildMigration($name)
235
    {
236
        $stub = $this->files->get(__DIR__ . '/../Stubs/migration.stub');
237
238
        $className = 'Create' . Str::plural($name). 'Table';
239
240
        $stub = str_replace('MIGRATION_CLASS_PLACEHOLDER', $className, $stub);
241
242
        $table = strtolower(Str::plural($name));
243
244
        $stub = str_replace('TABLE_NAME_PLACEHOLDER', $table, $stub);
245
246
        $class = 'App\\' . $name;
247
        $model = new $class;
248
249
        $stub = str_replace('MIGRATION_COLUMNS_PLACEHOLDER', $this->buildTableColumns($model->migrationAttributes()), $stub);
250
251
        return $stub;
252
    }
253
254
    protected function buildModel($name)
255
    {
256
        $stub = $this->files->get(__DIR__ . '/../Stubs/model.stub');
257
258
        $stub = $this->replaceClassName($name, $stub);
259
260
        $stub = $this->addMigrationAttributes($this->argument('attributes'), $stub);
261
262
        $stub = $this->addModelAttributes('fillable', $this->argument('attributes'), $stub);
263
264
        $stub = $this->addModelAttributes('hidden', $this->argument('attributes'), $stub);
265
266
        return $stub;
267
    }
268
269
    public function convertModelToTableName($model)
270
    {
271
        return Str::plural(Str::snake($model));
272
    }
273
274
    public function buildMigrationFilename($model)
275
    {
276
        $table = $this->convertModelToTableName($model);
277
278
        return date('Y_m_d_his') . '_create_' . $table . '_table.php';
279
    }
280
281
    private function replaceClassName($name, $stub)
282
    {
283
        return str_replace('NAME_PLACEHOLDER', $name, $stub);
284
    }
285
286
    private function addMigrationAttributes($text, $stub)
287
    {
288
        $attributesAsArray = $this->parseAttributesFromInputString($text);
289
        $attributesAsText = $this->convertArrayToString($attributesAsArray);
290
291
        return str_replace('MIGRATION_ATTRIBUTES_PLACEHOLDER', $attributesAsText, $stub);
292
    }
293
294
    /**
295
     * Convert a pipe-separated list of attributes to an array.
296
     *
297
     * @param string $text The Pipe separated attributes
298
     * @return array
299
     */
300
    public function parseAttributesFromInputString($text)
301
    {
302
        $parts = explode('|', $text);
303
304
        $attributes = [];
305
306
        foreach ($parts as $part) {
307
            $components = explode(':', $part);
308
            $attributes[$components[0]] =
309
                isset($components[1]) ? explode(',', $components[1]) : [];
310
        }
311
312
        return $attributes;
313
314
    }
315
316
    /**
317
     * Convert a PHP array into a string version.
318
     *
319
     * @param $array
320
     *
321
     * @return string
322
     */
323
    public function convertArrayToString($array)
324
    {
325
        $string = '[';
326
327
        foreach ($array as $name => $properties) {
328
            $string .= '[';
329
            $string .= "'name' => '" . $name . "',";
330
331
            $string .= "'properties' => [";
332
            foreach ($properties as $property) {
333
                $string .= "'".$property."', ";
334
            }
335
            $string = rtrim($string, ', ');
336
            $string .= ']';
337
338
            $string .= '],';
339
        }
340
341
        $string = rtrim($string, ',');
342
343
        $string .= ']';
344
345
346
        return $string;
347
    }
348
349
    public function addModelAttributes($name, $attributes, $stub)
350
    {
351
        $attributes = '[' . collect($this->parseAttributesFromInputString($attributes))
352
            ->filter(function($attribute) use ($name) {
353
                return in_array($name, $attribute);
354
            })->map(function ($attributes, $name) {
355
                return "'" . $name . "'";
356
            })->values()->implode(', ') . ']';
357
358
359
        return str_replace(strtoupper($name) . '_PLACEHOLDER', $attributes, $stub);
360
    }
361
362
    public function buildTableColumns($attributes)
363
    {
364
365
        return rtrim(collect($attributes)->reduce(function($column, $attribute) {
366
367
            $fieldType = $this->getFieldTypeFromProperties($attribute['properties']);
368
369
            if ($length = $this->typeCanDefineSize($fieldType)) {
370
                $length = $this->extractFieldLengthValue($attribute['properties']);
371
            }
372
373
            $properties = $this->extractAttributePropertiesToApply($attribute['properties']);
374
375
            return $column . $this->buildSchemaColumn($fieldType, $attribute['name'], $length, $properties);
376
377
        }));
378
379
    }
380
381
    /**
382
     * Get the column field type based from the properties of the field being created.
383
     *
384
     * @param array $properties
385
     * @return string
386
     */
387
    private function getFieldTypeFromProperties($properties)
388
    {
389
        $type = array_intersect($properties, $this->dataTypes);
390
391
        // If the properties that were given in the command
392
        // do not explicitly define a data type, or there
393
        // is no matching data type found, the column
394
        // should be cast to a string.
395
396
        if (! $type) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $type of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
397
            return 'string';
398
        }
399
400
        return $type[0];
401
    }
402
403
    /**
404
     * Can the data type have it's size controlled within the migration?
405
     *
406
     * @param string $type
407
     * @return bool
408
     */
409
    private function typeCanDefineSize($type)
410
    {
411
        return $type == 'string' || $type == 'char';
412
    }
413
414
    /**
415
     * Extract a numeric length value from all properties specified for the attribute.
416
     *
417
     * @param array $properties
418
     * @return int $length
419
     */
420
    private function extractFieldLengthValue($properties)
421
    {
422
        foreach ($properties as $property) {
423
            if (is_numeric($property)) {
424
                return $property;
425
            }
426
        }
427
428
        return 0;
429
    }
430
431
    /**
432
     * Get the column properties that should be applied to the column.
433
     *
434
     * @param $properties
435
     * @return array
436
     */
437
    private function extractAttributePropertiesToApply($properties)
438
    {
439
        return array_intersect($properties, $this->columnProperties);
440
    }
441
442
    /**
443
     * Create a Schema Builder column.
444
     *
445
     * @param string $fieldType The type of column to create
446
     * @param string $name Name of the column to create
447
     * @param int $length Field length
448
     * @param array $traits Additional properties to apply to the column
449
     * @return string
450
     */
451
    private function buildSchemaColumn($fieldType, $name, $length = 0, $traits = [])
452
    {
453
        return sprintf("\$table->%s('%s'%s)%s;" . PHP_EOL . '            ',
454
            $fieldType,
455
            $name,
456
            $length > 0 ? ", $length" : '',
457
            implode('', array_map(function ($trait) {
458
                return '->' . $trait . '()';
459
            }, $traits))
460
        );
461
    }
462
463
    /**
464
     * Build a Model name from a word.
465
     *
466
     * @param string $name
467
     * @return string
468
     */
469
    private function modelName($name)
470
    {
471
        return ucfirst($name);
472
    }
473
474
}
475