Completed
Push — master ( d690fb...22fdaf )
by wen
16:44 queued 10s
created

ComponentMakeCommand::createObserver()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 0
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 Sco\Admin\Facades\AdminElement;
11
use Symfony\Component\Console\Input\InputOption;
12
13
class ComponentMakeCommand extends GeneratorCommand
14
{
15
    /**
16
     * The console command name.
17
     *
18
     * @var string
19
     */
20
    protected $name = 'make:component';
21
22
    /**
23
     * The console command description.
24
     *
25
     * @var string
26
     */
27
    protected $description = 'Create a new component class(ScoAdmin)';
28
29
    /**
30
     * The type of class being generated.
31
     *
32
     * @var string
33
     */
34
    protected $type = 'Component';
35
36
    protected $columnTypeMappings = [
37
        'smallint' => 'text',
38
        'integer'  => 'text',
39
        'bigint'   => 'text',
40
        'float'    => 'text',
41
        'string'   => 'text',
42
        'text'     => 'text',
43
        'boolean'  => 'mapping',
44
        'datetime' => 'datetime',
45
        'date'     => 'datetime',
46
    ];
47
48
    protected $elementTypeMappings = [
49
        'smallint' => 'number',
50
        'integer'  => 'number',
51
        'bigint'   => 'number',
52
        'float'    => 'number',
53
        'string'   => 'text',
54
        'text'     => 'textarea',
55
        'boolean'  => 'elswitch',
56
        'datetime' => 'datetime',
57
        'date'     => 'date',
58
        'time'     => 'time',
59
    ];
60
61
    /**
62
     * Execute the console command.
63
     *
64
     * @return void
65
     */
66
    public function handle()
67
    {
68
        if (parent::handle() === false && !$this->option('force')) {
69
            return;
70
        }
71
72
        if ($this->hasOption('observer')) {
73
            $this->createObserver();
74
        }
75
    }
76
77
    /**
78
     * Create a new permission observer for the component
79
     */
80
    protected function createObserver()
81
    {
82
        $this->call('make:observer', [
83
            'name' => $this->getObserverName(),
84
        ]);
85
    }
86
87
    protected function getObserverName()
88
    {
89
        return $this->option('observer') ?? ($this->getNameInput() . 'Observer');
90
    }
91
92
    /**
93
     * Get the fully-qualified class name.
94
     *
95
     * @param string $class
96
     *
97
     * @return string
98
     */
99 View Code Duplication
    protected function parseClass($class)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
100
    {
101
        if (preg_match('([^A-Za-z0-9_/\\\\])', $class)) {
102
            throw new InvalidArgumentException('Class name contains invalid characters.');
103
        }
104
105
        $class = trim(str_replace('/', '\\', $class), '\\');
106
107
        if (!Str::startsWith($class, $rootNamespace = $this->laravel->getNamespace())) {
108
            $class = $rootNamespace . $class;
109
        }
110
111
        return $class;
112
    }
113
114
    /**
115
     * Get the stub file for the generator.
116
     *
117
     * @return string
118
     */
119
    protected function getStub()
120
    {
121
        if ($this->option('model')) {
122
            return __DIR__ . '/stubs/component-model.stub';
123
        }
124
125
        return __DIR__ . '/stubs/component.stub';
126
    }
127
128
    /**
129
     * Build the class with the given name.
130
     *
131
     * @param string $name
132
     *
133
     * @return string
134
     */
135
    protected function buildClass($name)
136
    {
137
        $replace = $this->buildObserverReplacements();
138
139
        if ($this->option('model')) {
140
            $replace = $this->buildModelReplacements($replace);
0 ignored issues
show
Unused Code introduced by
$replace is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
141
        }
142
143
144
        return str_replace(
145
            ['DummyObserver', 'DummyModel', 'DummyColumns', 'DummyElements'],
146
            [$observer, $model, implode("\n", $columns), implode("\n", $elements)],
0 ignored issues
show
Bug introduced by
The variable $observer does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $model does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $columns does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $elements does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
147
            parent::buildClass($name)
148
        );
149
    }
150
151
    protected function buildObserverReplacements()
152
    {
153
        if ($this->hasOption('observer')) {
154
            $observer = $this->getObserverName();
155
        } else {
156
            $observer = \Sco\Admin\Component\Observer::class;
157
        }
158
159
        if (!Str::startsWith($observer, [
160
            $this->laravel->getNamespace(),
161
            '\\',
162
        ])) {
163
            $observer = $this->laravel->getNamespace()
164
                . '\\' . $this->getComponentNamespace()
165
                . '\Observers\\' . $observer;
166
        }
167
168
        return [
169
            'DummyObserverClass'     => class_basename($observer),
170
            'DummyFullObserverClass' => $observer,
171
        ];
172
    }
173
174
    protected function buildModelReplacements(array $replace)
175
    {
176
        $modelClass = $this->parseModel($this->option('model'));
0 ignored issues
show
Bug introduced by
It seems like $this->option('model') targeting Illuminate\Console\Command::option() can also be of type array; however, Sco\Admin\Console\Compon...keCommand::parseModel() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
177
178
        if (!class_exists($modelClass)) {
179
            if ($this->confirm("A {$modelClass} model does not exist. Do you want to generate it?",
180
                true)) {
181
                $this->call('make:model', ['name' => $modelClass]);
182
            }
183
        }
184
185
        $columns  = $this->getViewColumns($modelClass);
186
        $elements = $this->getFormElements($modelClass);
187
188
        return array_merge($replace, [
189
            'DummyFullModelClass' => $modelClass,
190
            'DummyModelClass'     => class_basename($modelClass),
191
            'DummyColumns'        => $columns ? implode("\n", $columns) : '',
192
            'DummyElements'       => $elements ? implode("\n", $elements) : '',
193
        ]);
194
    }
195
196
    /**
197
     * Get the fully-qualified model class name.
198
     *
199
     * @param  string $model
200
     *
201
     * @return string
202
     */
203 View Code Duplication
    protected function parseModel($model)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
204
    {
205
        if (preg_match('([^A-Za-z0-9_/\\\\])', $model)) {
206
            throw new InvalidArgumentException('Model name contains invalid characters.');
207
        }
208
209
        $model = trim(str_replace('/', '\\', $model), '\\');
210
211
        if (!Str::startsWith($model, $rootNamespace = $this->laravel->getNamespace())) {
212
            $model = $rootNamespace . $model;
213
        }
214
215
        return $model;
216
    }
217
218 View Code Duplication
    protected function getViewColumns($model)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
219
    {
220
        $columns = $this->getTableColumns($model);
221
        if (!$columns) {
222
            return;
223
        }
224
225
        $list = [];
226
        foreach ($columns as $column) {
227
            $list[] = $this->buildViewColumn($column);
228
        }
229
        return $list;
230
    }
231
232
    protected function buildViewColumn(Column $column)
233
    {
234
        return sprintf(
235
            "            AdminColumn::%s('%s', '%s'),",
236
            $this->getViewColumnType($column->getType()->getName()),
237
            $column->getName(),
238
            $this->getColumnTitle($column)
239
        );
240
    }
241
242
    protected function getColumnTitle(Column $column)
243
    {
244
        return $column->getComment() ?? studly_case($column->getName());
245
    }
246
247
    protected function getViewColumnType($name)
248
    {
249
        return $this->columnTypeMappings[$name] ?? 'text';
250
    }
251
252 View Code Duplication
    protected function getFormElements($model)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
253
    {
254
        $columns = $this->getTableColumns($model);
255
256
        if (!$columns) {
257
            return;
258
        }
259
260
        $list = [];
261
        foreach ($columns as $column) {
262
            if (!$column->getAutoincrement()) {
263
                $list[] = $this->buildFormElement($column);
264
            }
265
        }
266
        return $list;
267
    }
268
269
    protected function buildFormElement(Column $column)
270
    {
271
        return sprintf(
272
            "            AdminElement::%s('%s', '%s')->required(),",
273
            $this->getFormElementType($column->getType()->getName()),
274
            $column->getName(),
275
            $this->getColumnTitle($column)
276
        );
277
    }
278
279
    protected function getFormElementType($name)
280
    {
281
        return $this->elementTypeMappings[$name] ?? 'text';
282
    }
283
284
    protected function getTableColumns($class)
285
    {
286
        if (empty($class)) {
287
            return;
288
        }
289
290
        if (!class_exists($class)) {
291
            return;
292
        }
293
294
        $model = new $class();
295
        if (!($model instanceof Model)) {
296
            return;
297
        }
298
        $schema = $model->getConnection()->getDoctrineSchemaManager();
299
300
        $table = $model->getConnection()->getTablePrefix() . $model->getTable();
301
302
        return $schema->listTableColumns($table);
303
    }
304
305
    /**
306
     * Get the default namespace for the class.
307
     *
308
     * @param  string $rootNamespace
309
     *
310
     * @return string
311
     */
312
    protected function getDefaultNamespace($rootNamespace)
313
    {
314
        return $rootNamespace . '\\' . $this->getComponentNamespace();
315
    }
316
317
    protected function getComponentNamespace()
318
    {
319
        return str_replace(
320
            '/',
321
            '\\',
322
            Str::after(
323
                config('admin.components'),
324
                app_path() . DIRECTORY_SEPARATOR)
325
        );
326
    }
327
328
    /**
329
     * Get the console command options.
330
     *
331
     * @return array
332
     */
333
    protected function getOptions()
334
    {
335
        return [
336
            [
337
                'observer', 'o', InputOption::VALUE_OPTIONAL,
338
                'Generate a new access observer for the component.',
339
            ],
340
            [
341
                'force', null, InputOption::VALUE_NONE,
342
                'Generate the class even if the component already exists.',
343
            ],
344
            [
345
                'model', 'm', InputOption::VALUE_OPTIONAL,
346
                'Generate a model for the component.',
347
            ],
348
        ];
349
    }
350
}
351