1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Sco\Admin\Console; |
4
|
|
|
|
5
|
|
|
use Doctrine\DBAL\Schema\Column; |
6
|
|
|
use Illuminate\Console\GeneratorCommand; |
7
|
|
|
use Illuminate\Database\Eloquent\Model; |
8
|
|
|
use Illuminate\Support\Str; |
9
|
|
|
use InvalidArgumentException; |
10
|
|
|
use Symfony\Component\Console\Input\InputOption; |
11
|
|
|
|
12
|
|
|
class ComponentMakeCommand extends GeneratorCommand |
13
|
|
|
{ |
14
|
|
|
/** |
15
|
|
|
* The console command name. |
16
|
|
|
* |
17
|
|
|
* @var string |
18
|
|
|
*/ |
19
|
|
|
protected $name = 'make:component'; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* The console command description. |
23
|
|
|
* |
24
|
|
|
* @var string |
25
|
|
|
*/ |
26
|
|
|
protected $description = 'Create a new component class(ScoAdmin)'; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* The type of class being generated. |
30
|
|
|
* |
31
|
|
|
* @var string |
32
|
|
|
*/ |
33
|
|
|
protected $type = 'Component'; |
34
|
|
|
|
35
|
|
|
protected $displayTypes = ['table', 'image', 'tree']; |
36
|
|
|
|
37
|
|
|
protected $columnTypeMappings = [ |
38
|
|
|
'smallint' => 'text', |
39
|
|
|
'integer' => 'text', |
40
|
|
|
'bigint' => 'text', |
41
|
|
|
'float' => 'text', |
42
|
|
|
'string' => 'text', |
43
|
|
|
'text' => 'text', |
44
|
|
|
'boolean' => 'mapping', |
45
|
|
|
'datetime' => 'datetime', |
46
|
|
|
'date' => 'datetime', |
47
|
|
|
]; |
48
|
|
|
|
49
|
|
|
protected $elementTypeMappings = [ |
50
|
|
|
'smallint' => 'number', |
51
|
|
|
'integer' => 'number', |
52
|
|
|
'bigint' => 'number', |
53
|
|
|
'float' => 'number', |
54
|
|
|
'string' => 'text', |
55
|
|
|
'text' => 'textarea', |
56
|
|
|
'boolean' => 'elswitch', |
57
|
|
|
'datetime' => 'datetime', |
58
|
|
|
'date' => 'date', |
59
|
|
|
'time' => 'time', |
60
|
|
|
]; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Get the stub file for the generator. |
64
|
|
|
* |
65
|
|
|
* @return string |
66
|
|
|
*/ |
67
|
|
|
protected function getStub() |
68
|
|
|
{ |
69
|
|
|
return __DIR__ . '/stubs/component.stub'; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Build the class with the given name. |
74
|
|
|
* |
75
|
|
|
* @param string $name |
76
|
|
|
* |
77
|
|
|
* @return string |
78
|
|
|
*/ |
79
|
|
|
protected function buildClass($name) |
80
|
|
|
{ |
81
|
|
|
$replace = array_merge( |
82
|
|
|
['DummyDisplayType' => $this->getDisplayType()], |
83
|
|
|
$this->buildObserverReplacements(), |
84
|
|
|
$this->buildModelReplacements() |
85
|
|
|
); |
86
|
|
|
|
87
|
|
|
return str_replace( |
88
|
|
|
array_keys($replace), |
89
|
|
|
array_values($replace), |
90
|
|
|
parent::buildClass($name) |
91
|
|
|
); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
protected function getDisplayType() |
95
|
|
|
{ |
96
|
|
|
$type = $this->option('display'); |
97
|
|
|
if (empty($type) || ! in_array($type, $this->displayTypes)) { |
98
|
|
|
$type = $this->choice( |
99
|
|
|
'What is Display type?', |
100
|
|
|
$this->displayTypes, |
101
|
|
|
0 |
102
|
|
|
); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
return $type; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
protected function buildObserverReplacements() |
109
|
|
|
{ |
110
|
|
View Code Duplication |
if (! ($observer = $this->option('observer'))) { |
|
|
|
|
111
|
|
|
$observer = $this->anticipate( |
112
|
|
|
'What is the observer class name?', |
113
|
|
|
[ |
114
|
|
|
$class = $this->parseObserver($this->getNameInput()) |
115
|
|
|
], |
116
|
|
|
$class |
117
|
|
|
); |
118
|
|
|
} |
119
|
|
|
$observerClass = $this->parseObserver($observer); |
|
|
|
|
120
|
|
|
|
121
|
|
|
if (! class_exists($observerClass)) { |
122
|
|
|
if ($this->confirm( |
123
|
|
|
"A {$observerClass} observer does not exist. Do you want to generate it?", |
124
|
|
|
true |
125
|
|
|
)) { |
126
|
|
|
$this->call('make:observer', [ |
127
|
|
|
'name' => $observerClass, |
128
|
|
|
]); |
129
|
|
|
} |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
return [ |
133
|
|
|
'DummyFullObserverClass' => $observerClass, |
134
|
|
|
'DummyObserverClass' => class_basename($observerClass), |
135
|
|
|
]; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Get the fully-qualified observer class name. |
140
|
|
|
* |
141
|
|
|
* @param string $observer |
142
|
|
|
* |
143
|
|
|
* @return string |
144
|
|
|
*/ |
145
|
|
|
protected function parseObserver($observer) |
146
|
|
|
{ |
147
|
|
|
if (preg_match('([^A-Za-z0-9_/\\\\])', $observer)) { |
148
|
|
|
throw new InvalidArgumentException('Observer name contains invalid characters.'); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
$observer = trim(str_replace('/', '\\', $observer), '\\Observer'); |
152
|
|
|
|
153
|
|
|
if (! Str::startsWith( |
154
|
|
|
$observer, |
155
|
|
|
$rootNamespace = $this->laravel->getNamespace() |
156
|
|
|
)) { |
157
|
|
|
$observer = $rootNamespace . $this->getComponentNamespace() |
158
|
|
|
. '\Observers\\' |
159
|
|
|
. $observer . 'Observer'; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
return $observer; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
protected function buildModelReplacements() |
166
|
|
|
{ |
167
|
|
View Code Duplication |
if (! ($model = $this->option('model'))) { |
|
|
|
|
168
|
|
|
$model = $this->anticipate( |
169
|
|
|
'What is the model class name?', |
170
|
|
|
[ |
171
|
|
|
$class = $this->parseModel($this->getNameInput()) |
172
|
|
|
], |
173
|
|
|
$class |
174
|
|
|
); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
$modelClass = $this->parseModel($model); |
|
|
|
|
178
|
|
|
|
179
|
|
|
if (! class_exists($modelClass)) { |
180
|
|
|
if ($this->confirm( |
181
|
|
|
"A {$modelClass} model does not exist. Do you want to generate it?", |
182
|
|
|
true |
183
|
|
|
)) { |
184
|
|
|
$this->call('make:model', ['name' => $modelClass]); |
185
|
|
|
} |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
$columns = $this->getViewColumns($modelClass); |
189
|
|
|
$elements = $this->getFormElements($modelClass); |
190
|
|
|
|
191
|
|
|
return [ |
192
|
|
|
'DummyFullModelClass' => $modelClass, |
193
|
|
|
'DummyColumns' => $columns ? implode("\n", $columns) : '', |
194
|
|
|
'DummyElements' => $elements ? implode("\n", $elements) : '', |
195
|
|
|
]; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Get the fully-qualified model class name. |
200
|
|
|
* |
201
|
|
|
* @param string $model |
202
|
|
|
* |
203
|
|
|
* @return string |
204
|
|
|
*/ |
205
|
|
|
protected function parseModel($model) |
206
|
|
|
{ |
207
|
|
|
if (preg_match('([^A-Za-z0-9_/\\\\])', $model)) { |
208
|
|
|
throw new InvalidArgumentException('Model name contains invalid characters.'); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
$model = trim(str_replace('/', '\\', $model), '\\'); |
212
|
|
|
|
213
|
|
|
if (! Str::startsWith($model, $rootNamespace = $this->laravel->getNamespace())) { |
214
|
|
|
$model = $rootNamespace . $model; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
return $model; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
View Code Duplication |
protected function getViewColumns($model) |
|
|
|
|
221
|
|
|
{ |
222
|
|
|
$columns = $this->getTableColumns($model); |
223
|
|
|
if (! $columns) { |
224
|
|
|
return; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
$list = []; |
228
|
|
|
foreach ($columns as $column) { |
229
|
|
|
$list[] = $this->buildViewColumn($column); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
return $list; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
protected function buildViewColumn(Column $column) |
236
|
|
|
{ |
237
|
|
|
return sprintf( |
238
|
|
|
" AdminColumn::%s('%s', '%s'),", |
239
|
|
|
$this->getViewColumnType($column->getType()->getName()), |
240
|
|
|
$column->getName(), |
241
|
|
|
$this->getColumnTitle($column) |
242
|
|
|
); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
protected function getColumnTitle(Column $column) |
246
|
|
|
{ |
247
|
|
|
return $column->getComment() ?? studly_case($column->getName()); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
protected function getViewColumnType($name) |
251
|
|
|
{ |
252
|
|
|
return $this->columnTypeMappings[$name] ?? 'text'; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
View Code Duplication |
protected function getFormElements($model) |
|
|
|
|
256
|
|
|
{ |
257
|
|
|
$columns = $this->getTableColumns($model); |
258
|
|
|
|
259
|
|
|
if (! $columns) { |
260
|
|
|
return; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
$list = []; |
264
|
|
|
foreach ($columns as $column) { |
265
|
|
|
if (! $column->getAutoincrement()) { |
266
|
|
|
$list[] = $this->buildFormElement($column); |
267
|
|
|
} |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
return $list; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
protected function buildFormElement(Column $column) |
274
|
|
|
{ |
275
|
|
|
return sprintf( |
276
|
|
|
" AdminElement::%s('%s', '%s')->required(),", |
277
|
|
|
$this->getFormElementType($column->getType()->getName()), |
278
|
|
|
$column->getName(), |
279
|
|
|
$this->getColumnTitle($column) |
280
|
|
|
); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
protected function getFormElementType($name) |
284
|
|
|
{ |
285
|
|
|
return $this->elementTypeMappings[$name] ?? 'text'; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
protected function getTableColumns($class) |
289
|
|
|
{ |
290
|
|
|
if (empty($class)) { |
291
|
|
|
return; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
if (! class_exists($class)) { |
295
|
|
|
return; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
$model = new $class(); |
299
|
|
|
if (! ($model instanceof Model)) { |
300
|
|
|
return; |
301
|
|
|
} |
302
|
|
|
$schema = $model->getConnection()->getDoctrineSchemaManager(); |
303
|
|
|
|
304
|
|
|
$table = $model->getConnection()->getTablePrefix() . $model->getTable(); |
305
|
|
|
|
306
|
|
|
return $schema->listTableColumns($table); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* Get the default namespace for the class. |
311
|
|
|
* |
312
|
|
|
* @param string $rootNamespace |
313
|
|
|
* |
314
|
|
|
* @return string |
315
|
|
|
*/ |
316
|
|
|
protected function getDefaultNamespace($rootNamespace) |
317
|
|
|
{ |
318
|
|
|
return $rootNamespace . '\\' . $this->getComponentNamespace(); |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* |
323
|
|
|
* @return string |
324
|
|
|
*/ |
325
|
|
|
protected function getComponentNamespace() |
326
|
|
|
{ |
327
|
|
|
return str_replace( |
328
|
|
|
'/', |
329
|
|
|
'\\', |
330
|
|
|
Str::after( |
331
|
|
|
config('admin.components'), |
332
|
|
|
app_path() . DIRECTORY_SEPARATOR |
333
|
|
|
) |
334
|
|
|
); |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
/** |
338
|
|
|
* Get the console command options. |
339
|
|
|
* |
340
|
|
|
* @return array |
341
|
|
|
*/ |
342
|
|
|
protected function getOptions() |
343
|
|
|
{ |
344
|
|
|
return [ |
345
|
|
|
[ |
346
|
|
|
'observer', |
347
|
|
|
'o', |
348
|
|
|
InputOption::VALUE_OPTIONAL, |
349
|
|
|
'Generate a new access observer for the component.', |
350
|
|
|
], |
351
|
|
|
[ |
352
|
|
|
'force', |
353
|
|
|
null, |
354
|
|
|
InputOption::VALUE_NONE, |
355
|
|
|
'Generate the class even if the component already exists.', |
356
|
|
|
], |
357
|
|
|
[ |
358
|
|
|
'model', |
359
|
|
|
'm', |
360
|
|
|
InputOption::VALUE_OPTIONAL, |
361
|
|
|
'Generate a model for the component.', |
362
|
|
|
], |
363
|
|
|
[ |
364
|
|
|
'display', |
365
|
|
|
'd', |
366
|
|
|
InputOption::VALUE_OPTIONAL, |
367
|
|
|
'Choose a type of data display' |
368
|
|
|
] |
369
|
|
|
]; |
370
|
|
|
} |
371
|
|
|
} |
372
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.