Completed
Push — master ( a2dd34...253c8c )
by Song
03:46
created

ResourceGenerator   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 245
Duplicated Lines 12.65 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
dl 31
loc 245
rs 9.0399
c 0
b 0
f 0
wmc 42
lcom 1
cbo 6

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getModel() 0 12 5
D generateForm() 0 84 25
A generateShow() 17 17 2
A generateGrid() 14 14 2
A getReservedColumns() 0 9 1
A getTableColumns() 0 28 5
A formatLabel() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ResourceGenerator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ResourceGenerator, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Encore\Admin\Console;
4
5
use Illuminate\Database\Eloquent\Model;
6
7
class ResourceGenerator
8
{
9
    /**
10
     * @var Model
11
     */
12
    protected $model;
13
14
    /**
15
     * @var array
16
     */
17
    protected $formats = [
18
        'form_field'  => "\$form->%s('%s', '%s')",
19
        'show_field'  => "\$show->%s('%s')",
20
        'grid_column' => "\$grid->%s('%s')",
21
    ];
22
23
    /**
24
     * @var array
25
     */
26
    private $doctrineTypeMapping = [
27
        'string' => [
28
            'enum', 'geometry', 'geometrycollection', 'linestring',
29
            'polygon', 'multilinestring', 'multipoint', 'multipolygon',
30
            'point',
31
        ]
32
    ];
33
34
    /**
35
     * @var array
36
     */
37
    protected $fieldTypeMapping = [
38
        'ip'       => 'ip',
39
        'email'    => 'email|mail',
40
        'password' => 'password|pwd',
41
        'url'      => 'url|link|src|href',
42
        'mobile'   => 'mobile|phone',
43
        'color'    => 'color|rgb',
44
        'image'    => 'image|img|avatar|pic|picture|cover',
45
        'file'     => 'file|attachment'
46
    ];
47
48
    /**
49
     * ResourceGenerator constructor.
50
     *
51
     * @param mixed $model
52
     */
53
    public function __construct($model)
54
    {
55
        $this->model = $this->getModel($model);
56
    }
57
58
    /**
59
     * @param mixed $model
60
     * @return mixed
61
     */
62
    protected function getModel($model)
63
    {
64
        if ($model instanceof Model) {
65
            return $model;
66
        }
67
68
        if (!class_exists($model) || !is_string($model) || !is_subclass_of($model, Model::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Illuminate\Database\Eloquent\Model::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
69
            throw new \InvalidArgumentException("Invalid model [$model] !");
70
        }
71
72
        return new $model;
73
    }
74
75
    /**
76
     * @return string
77
     */
78
    public function generateForm()
79
    {
80
        $reservedColumns = $this->getReservedColumns();
81
82
        $output = '';
83
84
        foreach ($this->getTableColumns() as $column) {
85
            $name = $column->getName();
86
            if (in_array($name, $reservedColumns)) {
87
                continue;
88
            }
89
            $type    = $column->getType()->getName();
90
            $default = $column->getDefault();
91
92
            $defaultValue = '';
93
94
            // set column fieldType and defaultValue
95
            switch ($type) {
96
                case 'boolean':
97
                case 'bool':
98
                    $fieldType = 'switch';
99
                    break;
100
                case 'json':
101
                case 'array':
102
                case 'object':
103
                    $fieldType = 'text';
104
                    break;
105
                case 'string':
106
                    $fieldType = 'text';
107
                    foreach ($this->fieldTypeMapping as $type => $regex) {
108
                        if (preg_match("/^($regex)$/i", $name) !== 0) {
109
                            $fieldType = $type;
110
                            break;
111
                        }
112
                    }
113
                    $defaultValue = "'{$default}'";
114
                    break;
115
                case 'integer':
116
                case 'bigint':
117
                case 'smallint':
118
                case 'timestamp':
119
                    $fieldType = 'number';
120
                    break;
121
                case 'decimal':
122
                case 'float':
123
                case 'real':
124
                    $fieldType = 'decimal';
125
                    break;
126
                case 'datetime':
127
                    $fieldType    = 'datetime';
128
                    $defaultValue = "date('Y-m-d H:i:s')";
129
                    break;
130
                case 'date':
131
                    $fieldType    = 'date';
132
                    $defaultValue = "date('Y-m-d')";
133
                    break;
134
                case 'time':
135
                    $fieldType    = 'time';
136
                    $defaultValue = "date('H:i:s')";
137
                    break;
138
                case 'text':
139
                case 'blob':
140
                    $fieldType = 'textarea';
141
                    break;
142
                default:
143
                    $fieldType    = 'text';
144
                    $defaultValue = "'{$default}'";
145
            }
146
147
            $defaultValue = $defaultValue ?: $default;
148
149
            $label = $this->formatLabel($name);
150
151
            $output .= sprintf($this->formats['form_field'], $fieldType, $name, $label);
152
153
            if (trim($defaultValue, "'\"")) {
154
                $output .= "->default({$defaultValue})";
155
            }
156
157
            $output .= ";\r\n";
158
        }
159
160
        return $output;
161
    }
162
163 View Code Duplication
    public function generateShow()
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...
164
    {
165
        $output = '';
166
167
        foreach ($this->getTableColumns() as $column) {
168
            $name    = $column->getName();
169
170
            // set column label
171
            $label = $this->formatLabel($name);
172
173
            $output .= sprintf($this->formats['show_field'], $name, $label);
174
175
            $output .= ";\r\n";
176
        }
177
178
        return $output;
179
    }
180
181 View Code Duplication
    public function generateGrid()
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...
182
    {
183
        $output = '';
184
185
        foreach ($this->getTableColumns() as $column) {
186
            $name    = $column->getName();
187
            $label = $this->formatLabel($name);
188
189
            $output .= sprintf($this->formats['grid_column'], $name, $label);
190
            $output .= ";\r\n";
191
        }
192
193
        return $output;
194
    }
195
196
    protected function getReservedColumns()
197
    {
198
        return [
199
            $this->model->getKeyName(),
200
            $this->model->getCreatedAtColumn(),
201
            $this->model->getUpdatedAtColumn(),
202
            'deleted_at',
203
        ];
204
    }
205
206
    /**
207
     * Get columns of a giving model.
208
     *
209
     * @return \Doctrine\DBAL\Schema\Column[]
210
     * @throws \Exception
211
     */
212
    protected function getTableColumns()
213
    {
214
        if (!$this->model->getConnection()->isDoctrineAvailable()) {
215
            throw new \Exception(
216
                'You need to require doctrine/dbal: ~2.3 in your own composer.json to get database columns. '
217
            );
218
        }
219
220
        $table = $this->model->getConnection()->getTablePrefix() . $this->model->getTable();
221
        /** @var \Doctrine\DBAL\Schema\MySqlSchemaManager $schema */
222
        $schema = $this->model->getConnection()->getDoctrineSchemaManager($table);
0 ignored issues
show
Unused Code introduced by
The call to Connection::getDoctrineSchemaManager() has too many arguments starting with $table.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
223
224
        // custom mapping the types that doctrine/dbal does not support
225
        $databasePlatform = $schema->getDatabasePlatform();
226
227
        foreach ($this->doctrineTypeMapping as $doctrineType => $dbTypes) {
228
            foreach ($dbTypes as $dbType) {
229
                $databasePlatform->registerDoctrineTypeMapping($dbType, $doctrineType);
230
            }
231
        }
232
233
        $database = null;
234
        if (strpos($table, '.')) {
235
            list($database, $table) = explode('.', $table);
236
        }
237
238
        return $schema->listTableColumns($table, $database);
239
    }
240
241
    /**
242
     * Format label.
243
     *
244
     * @param string $value
245
     * @return string
246
     */
247
    protected function formatLabel($value)
248
    {
249
        return ucfirst(str_replace(['-', '_'], ' ', $value));
250
    }
251
}