FieldGenerator   B
last analyzed

Complexity

Total Complexity 54

Size/Duplication

Total Lines 254
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 54
c 3
b 0
f 0
lcom 1
cbo 4
dl 0
loc 254
rs 7.0642

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getEnum() 0 16 3
F getFields() 0 73 28
A getLength() 0 6 3
B getDefault() 0 11 5
A getPrecision() 0 10 4
A getMultiFieldIndexes() 0 15 3
A argsToString() 0 11 2
A decorate() 0 9 2
A generate() 0 11 2
A setEnum() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like FieldGenerator 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 FieldGenerator, and based on these observations, apply Extract Interface, too.

1
<?php namespace Xethron\MigrationsGenerator\Generators;
2
3
use DB;
4
5
class FieldGenerator {
6
7
	/**
8
	 * Convert dbal types to Laravel Migration Types
9
	 * @var array
10
	 */
11
	protected $fieldTypeMap = [
12
		'tinyint'  => 'tinyInteger',
13
		'smallint' => 'smallInteger',
14
		'bigint'   => 'bigInteger',
15
		'datetime' => 'dateTime',
16
		'blob'     => 'binary',
17
	];
18
19
	/**
20
	 * @var string
21
	 */
22
	protected $database;
23
24
	/**
25
	 * Create array of all the fields for a table
26
	 *
27
	 * @param string                                      $table Table Name
28
	 * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema
29
	 * @param string                                      $database
30
	 * @param bool                                        $ignoreIndexNames
31
	 *
32
	 * @return array|bool
33
	 */
34
	public function generate($table, $schema, $database, $ignoreIndexNames)
35
	{
36
		$this->database = $database;
37
		$columns = $schema->listTableColumns( $table );
38
		if ( empty( $columns ) ) return false;
39
40
		$indexGenerator = new IndexGenerator($table, $schema, $ignoreIndexNames);
41
		$fields = $this->setEnum($this->getFields($columns, $indexGenerator), $table);
42
		$indexes = $this->getMultiFieldIndexes($indexGenerator);
43
		return array_merge($fields, $indexes);
44
	}
45
46
	/**
47
	 * Return all enum columns for a given table
48
	 * @param string $table
49
	 * @return array
50
	 */
51
	protected function getEnum($table)
52
	{
53
		try {
54
			$result = DB::table('information_schema.columns')
55
				->where('table_schema', $this->database)
56
				->where('table_name', $table)
57
				->where('data_type', 'enum')
58
				->get(['column_name','column_type']);
59
			if ($result)
60
				return $result;
61
			else
62
				return [];
63
		} catch (\Exception $e){
64
			return [];
65
		}
66
	}
67
68
	/**
69
	 * @param array $fields
70
	 * @param string $table
71
	 * @return array
72
	 */
73
	protected function setEnum(array $fields, $table)
74
	{
75
		foreach ($this->getEnum($table) as $column) {
76
			$fields[$column->column_name]['type'] = 'enum';
77
			$fields[$column->column_name]['args'] = str_replace('enum(', 'array(', $column->column_type);
78
		}
79
		return $fields;
80
	}
81
82
	/**
83
	 * @param \Doctrine\DBAL\Schema\Column[] $columns
84
	 * @param IndexGenerator $indexGenerator
85
	 * @return array
86
	 */
87
	protected function getFields($columns, IndexGenerator $indexGenerator)
88
	{
89
		$fields = array();
90
		foreach ($columns as $column) {
91
			$name = $column->getName();
92
			$type = $column->getType()->getName();
93
			$length = $column->getLength();
94
			$default = $column->getDefault();
95
			if (is_bool($default))
96
				$default = $default === true ? 1 : 0;
97
			$nullable = (!$column->getNotNull());
98
			$index = $indexGenerator->getIndex($name);
99
			$comment = $column->getComment();
100
			$decorators = null;
101
			$args = null;
102
103
			if (isset($this->fieldTypeMap[$type])) {
104
				$type = $this->fieldTypeMap[$type];
105
			}
106
107
			// Different rules for different type groups
108
			if (in_array($type, ['tinyInteger', 'smallInteger', 'integer', 'bigInteger'])) {
109
				// Integer
110
				if ($type == 'integer' and $column->getUnsigned() and $column->getAutoincrement()) {
111
					$type = 'increments';
112
					$index = null;
113
				} else {
114
					if ($column->getUnsigned()) {
115
						$decorators[] = 'unsigned';
116
					}
117
					if ($column->getAutoincrement()) {
118
						$args = 'true';
119
						$index = null;
120
					}
121
				}
122
			} elseif ($type == 'dateTime') {
123
				if ($name == 'deleted_at' and $nullable) {
124
					$nullable = false;
125
					$type = 'softDeletes';
126
					$name = '';
127
				} elseif ($name == 'created_at' and isset($fields['updated_at'])) {
128
					$fields['updated_at'] = ['field' => '', 'type' => 'timestamps'];
129
					continue;
130
				} elseif ($name == 'updated_at' and isset($fields['created_at'])) {
131
					$fields['created_at'] = ['field' => '', 'type' => 'timestamps'];
132
					continue;
133
				}
134
			} elseif (in_array($type, ['decimal', 'float', 'double'])) {
135
				// Precision based numbers
136
				$args = $this->getPrecision($column->getPrecision(), $column->getScale());
137
				if ($column->getUnsigned()) {
138
					$decorators[] = 'unsigned';
139
				}
140
			} else {
141
				// Probably not a number (string/char)
142
				if ($type === 'string' && $column->getFixed()) {
143
					$type = 'char';
144
				}
145
				$args = $this->getLength($length);
146
			}
147
148
			if ($nullable) $decorators[] = 'nullable';
149
			if ($default !== null) $decorators[] = $this->getDefault($default, $type);
150
			if ($index) $decorators[] = $this->decorate($index->type, $index->name);
151
			if ($comment) $decorators[] = "comment('" . addcslashes($comment, "\\'") . "')";
0 ignored issues
show
Bug Best Practice introduced by
The expression $comment of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
152
153
			$field = ['field' => $name, 'type' => $type];
154
			if ($decorators) $field['decorators'] = $decorators;
155
			if ($args) $field['args'] = $args;
156
			$fields[$name] = $field;
157
		}
158
		return $fields;
159
	}
160
161
	/**
162
	 * @param int $length
163
	 * @return int|void
164
	 */
165
	protected function getLength($length)
166
	{
167
		if ($length and $length !== 255) {
168
			return $length;
169
		}
170
	}
171
172
	/**
173
	 * @param string $default
174
	 * @param string $type
175
	 * @return string
176
	 */
177
	protected function getDefault($default, &$type)
178
	{
179
		if (in_array($default, ['CURRENT_TIMESTAMP'], true)) {
180
			if ($type == 'dateTime')
181
				$type = 'timestamp';
182
			$default = $this->decorate('DB::raw', $default);
183
		} elseif (in_array($type, ['string', 'text']) or !is_numeric($default)) {
184
			$default = $this->argsToString($default);
185
		}
186
		return $this->decorate('default', $default, '');
187
	}
188
189
	/**
190
	 * @param int $precision
191
	 * @param int $scale
192
	 * @return string|void
193
	 */
194
	protected function getPrecision($precision, $scale)
195
	{
196
		if ($precision != 8 or $scale != 2) {
197
			$result = $precision;
198
			if ($scale != 2) {
199
				$result .= ', ' . $scale;
200
			}
201
			return $result;
202
		}
203
	}
204
205
	/**
206
	 * @param string|array $args
207
	 * @param string       $quotes
208
	 * @return string
209
	 */
210
	protected function argsToString($args, $quotes = '\'')
211
	{
212
		if ( is_array( $args ) ) {
213
			$separator = $quotes .', '. $quotes;
214
			$args = implode($separator, str_replace($quotes, '\\'.$quotes, $args));
215
		} else {
216
			$args = str_replace($quotes, '\\'.$quotes, $args);
217
		}
218
219
		return $quotes . $args . $quotes;
220
	}
221
222
	/**
223
	 * Get Decorator
224
	 * @param string       $function
225
	 * @param string|array $args
226
	 * @param string       $quotes
227
	 * @return string
228
	 */
229
	protected function decorate($function, $args, $quotes = '\'')
230
	{
231
		if ( ! is_null( $args ) ) {
232
			$args = $this->argsToString($args, $quotes);
233
			return $function . '(' . $args . ')';
234
		} else {
235
			return $function;
236
		}
237
	}
238
239
	/**
240
	 * @param IndexGenerator $indexGenerator
241
	 * @return array
242
	 */
243
	protected function getMultiFieldIndexes(IndexGenerator $indexGenerator)
244
	{
245
		$indexes = array();
246
		foreach ($indexGenerator->getMultiFieldIndexes() as $index) {
247
			$indexArray = [
248
				'field' => $index->columns,
249
				'type' => $index->type,
250
			];
251
			if ($index->name) {
252
				$indexArray['args'] = $this->argsToString($index->name);
253
			}
254
			$indexes[] = $indexArray;
255
		}
256
		return $indexes;
257
	}
258
}
259