Complex classes like Schema 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 Schema, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
28 | class Schema extends \yii\db\Schema |
||
29 | { |
||
30 | use ConstraintFinderTrait; |
||
31 | |||
32 | /** |
||
33 | * @var bool whether MySQL used is older than 5.1. |
||
34 | */ |
||
35 | private $_oldMysql; |
||
36 | |||
37 | |||
38 | /** |
||
39 | * @var array mapping from physical column types (keys) to abstract column types (values) |
||
40 | */ |
||
41 | public $typeMap = [ |
||
42 | 'tinyint' => self::TYPE_SMALLINT, |
||
43 | 'bit' => self::TYPE_INTEGER, |
||
44 | 'smallint' => self::TYPE_SMALLINT, |
||
45 | 'mediumint' => self::TYPE_INTEGER, |
||
46 | 'int' => self::TYPE_INTEGER, |
||
47 | 'integer' => self::TYPE_INTEGER, |
||
48 | 'bigint' => self::TYPE_BIGINT, |
||
49 | 'float' => self::TYPE_FLOAT, |
||
50 | 'double' => self::TYPE_DOUBLE, |
||
51 | 'real' => self::TYPE_FLOAT, |
||
52 | 'decimal' => self::TYPE_DECIMAL, |
||
53 | 'numeric' => self::TYPE_DECIMAL, |
||
54 | 'tinytext' => self::TYPE_TEXT, |
||
55 | 'mediumtext' => self::TYPE_TEXT, |
||
56 | 'longtext' => self::TYPE_TEXT, |
||
57 | 'longblob' => self::TYPE_BINARY, |
||
58 | 'blob' => self::TYPE_BINARY, |
||
59 | 'text' => self::TYPE_TEXT, |
||
60 | 'varchar' => self::TYPE_STRING, |
||
61 | 'string' => self::TYPE_STRING, |
||
62 | 'char' => self::TYPE_CHAR, |
||
63 | 'datetime' => self::TYPE_DATETIME, |
||
64 | 'year' => self::TYPE_DATE, |
||
65 | 'date' => self::TYPE_DATE, |
||
66 | 'time' => self::TYPE_TIME, |
||
67 | 'timestamp' => self::TYPE_TIMESTAMP, |
||
68 | 'enum' => self::TYPE_STRING, |
||
69 | 'varbinary' => self::TYPE_BINARY, |
||
70 | ]; |
||
71 | |||
72 | /** |
||
73 | * @inheritDoc |
||
74 | */ |
||
75 | 40 | protected function resolveTableName($name) |
|
76 | { |
||
77 | 40 | $resolvedName = new TableSchema(); |
|
78 | 40 | $parts = explode('.', str_replace('`', '', $name)); |
|
79 | 40 | if (isset($parts[1])) { |
|
80 | $resolvedName->schemaName = $parts[0]; |
||
81 | $resolvedName->name = $parts[1]; |
||
82 | } else { |
||
83 | 40 | $resolvedName->schemaName = $this->defaultSchema; |
|
84 | 40 | $resolvedName->name = $name; |
|
85 | } |
||
86 | 40 | $resolvedName->fullName = ($resolvedName->schemaName !== $this->defaultSchema ? $resolvedName->schemaName . '.' : '') . $resolvedName->name; |
|
87 | 40 | return $resolvedName; |
|
88 | } |
||
89 | |||
90 | /** |
||
91 | * @inheritDoc |
||
92 | */ |
||
93 | 6 | protected function findTableNames($schema = '') |
|
94 | { |
||
95 | 6 | $sql = 'SHOW TABLES'; |
|
96 | 6 | if ($schema !== '') { |
|
97 | $sql .= ' FROM ' . $this->quoteSimpleTableName($schema); |
||
98 | } |
||
99 | |||
100 | 6 | return $this->db->createCommand($sql)->queryColumn(); |
|
101 | } |
||
102 | |||
103 | /** |
||
104 | * @inheritDoc |
||
105 | */ |
||
106 | 232 | protected function loadTableSchema($name) |
|
107 | { |
||
108 | 232 | $table = new TableSchema(); |
|
109 | 232 | $this->resolveTableNames($table, $name); |
|
110 | |||
111 | 232 | if ($this->findColumns($table)) { |
|
112 | 227 | $this->findConstraints($table); |
|
113 | 227 | return $table; |
|
114 | } |
||
115 | |||
116 | 13 | return null; |
|
117 | } |
||
118 | |||
119 | /** |
||
120 | * @inheritDoc |
||
121 | */ |
||
122 | 13 | protected function loadTablePrimaryKey($tableName) |
|
126 | |||
127 | /** |
||
128 | * @inheritDoc |
||
129 | */ |
||
130 | 4 | protected function loadTableForeignKeys($tableName) |
|
134 | |||
135 | /** |
||
136 | * @inheritDoc |
||
137 | */ |
||
138 | 10 | protected function loadTableIndexes($tableName) |
|
169 | |||
170 | /** |
||
171 | * @inheritDoc |
||
172 | */ |
||
173 | 13 | protected function loadTableUniques($tableName) |
|
177 | |||
178 | /** |
||
179 | * @inheritDoc |
||
180 | * @throws NotSupportedException if this method is called. |
||
181 | */ |
||
182 | 12 | protected function loadTableChecks($tableName) |
|
|
|||
183 | { |
||
184 | 12 | throw new NotSupportedException('MySQL does not support check constraints.'); |
|
185 | } |
||
186 | |||
187 | /** |
||
188 | * @inheritDoc |
||
189 | * @throws NotSupportedException if this method is called. |
||
190 | */ |
||
191 | 12 | protected function loadTableDefaultValues($tableName) |
|
192 | { |
||
193 | 12 | throw new NotSupportedException('MySQL does not support default value constraints.'); |
|
194 | } |
||
195 | |||
196 | /** |
||
197 | * Quotes a table name for use in a query. |
||
198 | * A simple table name has no schema prefix. |
||
199 | * @param string $name table name |
||
200 | * @return string the properly quoted table name |
||
201 | */ |
||
202 | 325 | public function quoteSimpleTableName($name) |
|
206 | |||
207 | /** |
||
208 | * Quotes a column name for use in a query. |
||
209 | * A simple column name has no prefix. |
||
210 | * @param string $name column name |
||
211 | * @return string the properly quoted column name |
||
212 | */ |
||
213 | 324 | public function quoteSimpleColumnName($name) |
|
217 | |||
218 | /** |
||
219 | * Creates a query builder for the MySQL database. |
||
220 | * @return QueryBuilder query builder instance |
||
221 | */ |
||
222 | 245 | public function createQueryBuilder() |
|
223 | { |
||
224 | 245 | return new QueryBuilder($this->db); |
|
225 | } |
||
226 | |||
227 | /** |
||
228 | * Resolves the table name and schema name (if any). |
||
229 | * @param TableSchema $table the table metadata object |
||
230 | * @param string $name the table name |
||
231 | */ |
||
232 | 232 | protected function resolveTableNames($table, $name) |
|
243 | |||
244 | /** |
||
245 | * Loads the column information into a [[ColumnSchema]] object. |
||
246 | * @param array $info column information |
||
247 | * @return ColumnSchema the column schema object |
||
248 | */ |
||
249 | 227 | protected function loadColumnSchema($info) |
|
250 | { |
||
251 | 227 | $column = $this->createColumnSchema(); |
|
252 | |||
253 | 227 | $column->name = $info['field']; |
|
254 | 227 | $column->allowNull = $info['null'] === 'YES'; |
|
255 | 227 | $column->isPrimaryKey = strpos($info['key'], 'PRI') !== false; |
|
256 | 227 | $column->autoIncrement = stripos($info['extra'], 'auto_increment') !== false; |
|
257 | 227 | $column->comment = $info['comment']; |
|
258 | |||
259 | 227 | $column->dbType = $info['type']; |
|
260 | 227 | $column->unsigned = stripos($column->dbType, 'unsigned') !== false; |
|
261 | |||
262 | 227 | $column->type = self::TYPE_STRING; |
|
263 | 227 | if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { |
|
264 | 227 | $type = strtolower($matches[1]); |
|
265 | 227 | if (isset($this->typeMap[$type])) { |
|
266 | 227 | $column->type = $this->typeMap[$type]; |
|
267 | } |
||
268 | 227 | if (!empty($matches[2])) { |
|
269 | 227 | if ($type === 'enum') { |
|
270 | 14 | preg_match_all("/'[^']*'/", $matches[2], $values); |
|
271 | 14 | foreach ($values[0] as $i => $value) { |
|
272 | 14 | $values[$i] = trim($value, "'"); |
|
273 | } |
||
274 | 14 | $column->enumValues = $values; |
|
275 | } else { |
||
276 | 227 | $values = explode(',', $matches[2]); |
|
277 | 227 | $column->size = $column->precision = (int) $values[0]; |
|
278 | 227 | if (isset($values[1])) { |
|
279 | 69 | $column->scale = (int) $values[1]; |
|
280 | } |
||
281 | 227 | if ($column->size === 1 && $type === 'bit') { |
|
282 | 5 | $column->type = 'boolean'; |
|
283 | 227 | } elseif ($type === 'bit') { |
|
284 | 14 | if ($column->size > 32) { |
|
285 | $column->type = 'bigint'; |
||
286 | 14 | } elseif ($column->size === 32) { |
|
287 | $column->type = 'integer'; |
||
288 | } |
||
289 | } |
||
290 | } |
||
291 | } |
||
292 | } |
||
293 | |||
294 | 227 | $column->phpType = $this->getColumnPhpType($column); |
|
295 | |||
296 | 227 | if (!$column->isPrimaryKey) { |
|
297 | 223 | if ($column->type === 'timestamp' && $info['default'] === 'CURRENT_TIMESTAMP') { |
|
298 | 15 | $column->defaultValue = new Expression('CURRENT_TIMESTAMP'); |
|
299 | 223 | } elseif (isset($type) && $type === 'bit') { |
|
300 | 15 | $column->defaultValue = bindec(trim($info['default'], 'b\'')); |
|
301 | } else { |
||
302 | 222 | $column->defaultValue = $column->phpTypecast($info['default']); |
|
303 | } |
||
304 | } |
||
305 | |||
306 | 227 | return $column; |
|
307 | } |
||
308 | |||
309 | /** |
||
310 | * Collects the metadata of table columns. |
||
311 | * @param TableSchema $table the table metadata |
||
312 | * @return bool whether the table exists in the database |
||
313 | * @throws \Exception if DB query fails |
||
314 | */ |
||
315 | 232 | protected function findColumns($table) |
|
316 | { |
||
317 | 232 | $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteTableName($table->fullName); |
|
318 | try { |
||
319 | 232 | $columns = $this->db->createCommand($sql)->queryAll(); |
|
320 | 13 | } catch (\Exception $e) { |
|
321 | 13 | $previous = $e->getPrevious(); |
|
322 | 13 | if ($previous instanceof \PDOException && strpos($previous->getMessage(), 'SQLSTATE[42S02') !== false) { |
|
323 | // table does not exist |
||
324 | // https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html#error_er_bad_table_error |
||
325 | 13 | return false; |
|
326 | } |
||
327 | throw $e; |
||
328 | } |
||
329 | 227 | foreach ($columns as $info) { |
|
330 | 227 | if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_LOWER) { |
|
331 | 226 | $info = array_change_key_case($info, CASE_LOWER); |
|
332 | } |
||
333 | 227 | $column = $this->loadColumnSchema($info); |
|
334 | 227 | $table->columns[$column->name] = $column; |
|
335 | 227 | if ($column->isPrimaryKey) { |
|
336 | 213 | $table->primaryKey[] = $column->name; |
|
337 | 213 | if ($column->autoIncrement) { |
|
338 | 227 | $table->sequenceName = ''; |
|
339 | } |
||
340 | } |
||
341 | } |
||
342 | |||
343 | 227 | return true; |
|
344 | } |
||
345 | |||
346 | /** |
||
347 | * Gets the CREATE TABLE sql string. |
||
348 | * @param TableSchema $table the table metadata |
||
349 | * @return string $sql the result of 'SHOW CREATE TABLE' |
||
350 | */ |
||
351 | 1 | protected function getCreateTableSql($table) |
|
352 | { |
||
353 | 1 | $row = $this->db->createCommand('SHOW CREATE TABLE ' . $this->quoteTableName($table->fullName))->queryOne(); |
|
354 | 1 | if (isset($row['Create Table'])) { |
|
355 | 1 | $sql = $row['Create Table']; |
|
356 | } else { |
||
357 | $row = array_values($row); |
||
358 | $sql = $row[1]; |
||
359 | } |
||
360 | |||
361 | 1 | return $sql; |
|
362 | } |
||
363 | |||
364 | /** |
||
365 | * Collects the foreign key column details for the given table. |
||
366 | * @param TableSchema $table the table metadata |
||
367 | * @throws \Exception |
||
368 | */ |
||
369 | 227 | protected function findConstraints($table) |
|
370 | { |
||
371 | $sql = <<<'SQL' |
||
372 | 227 | SELECT |
|
373 | kcu.constraint_name, |
||
374 | kcu.column_name, |
||
375 | kcu.referenced_table_name, |
||
376 | kcu.referenced_column_name |
||
377 | FROM information_schema.referential_constraints AS rc |
||
378 | JOIN information_schema.key_column_usage AS kcu ON |
||
379 | ( |
||
380 | kcu.constraint_catalog = rc.constraint_catalog OR |
||
381 | (kcu.constraint_catalog IS NULL AND rc.constraint_catalog IS NULL) |
||
382 | ) AND |
||
383 | kcu.constraint_schema = rc.constraint_schema AND |
||
384 | kcu.constraint_name = rc.constraint_name |
||
385 | WHERE rc.constraint_schema = database() AND kcu.table_schema = database() |
||
386 | AND rc.table_name = :tableName AND kcu.table_name = :tableName1 |
||
387 | SQL; |
||
388 | |||
389 | try { |
||
390 | 227 | $rows = $this->db->createCommand($sql, [':tableName' => $table->name, ':tableName1' => $table->name])->queryAll(); |
|
391 | 227 | $constraints = []; |
|
392 | |||
393 | 227 | foreach ($rows as $row) { |
|
394 | 157 | $constraints[$row['constraint_name']]['referenced_table_name'] = $row['referenced_table_name']; |
|
395 | 157 | $constraints[$row['constraint_name']]['columns'][$row['column_name']] = $row['referenced_column_name']; |
|
396 | } |
||
397 | |||
398 | 227 | $table->foreignKeys = []; |
|
399 | 227 | foreach ($constraints as $name => $constraint) { |
|
400 | 157 | $table->foreignKeys[$name] = array_merge( |
|
401 | 157 | [$constraint['referenced_table_name']], |
|
402 | 227 | $constraint['columns'] |
|
403 | ); |
||
404 | } |
||
405 | } catch (\Exception $e) { |
||
406 | $previous = $e->getPrevious(); |
||
407 | if (!$previous instanceof \PDOException || strpos($previous->getMessage(), 'SQLSTATE[42S02') === false) { |
||
408 | throw $e; |
||
409 | } |
||
410 | |||
411 | // table does not exist, try to determine the foreign keys using the table creation sql |
||
412 | $sql = $this->getCreateTableSql($table); |
||
413 | $regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi'; |
||
414 | if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) { |
||
415 | foreach ($matches as $match) { |
||
416 | $fks = array_map('trim', explode(',', str_replace('`', '', $match[1]))); |
||
417 | $pks = array_map('trim', explode(',', str_replace('`', '', $match[3]))); |
||
418 | $constraint = [str_replace('`', '', $match[2])]; |
||
419 | foreach ($fks as $k => $name) { |
||
420 | $constraint[$name] = $pks[$k]; |
||
421 | } |
||
422 | $table->foreignKeys[md5(serialize($constraint))] = $constraint; |
||
423 | } |
||
424 | $table->foreignKeys = array_values($table->foreignKeys); |
||
425 | } |
||
426 | } |
||
427 | 227 | } |
|
428 | |||
429 | /** |
||
430 | * Returns all unique indexes for the given table. |
||
431 | * Each array element is of the following structure: |
||
432 | * |
||
433 | * ```php |
||
434 | * [ |
||
435 | * 'IndexName1' => ['col1' [, ...]], |
||
436 | * 'IndexName2' => ['col2' [, ...]], |
||
437 | * ] |
||
438 | * ``` |
||
439 | * |
||
440 | * @param TableSchema $table the table metadata |
||
441 | * @return array all unique indexes for the given table. |
||
442 | */ |
||
443 | 1 | public function findUniqueIndexes($table) |
|
444 | { |
||
445 | 1 | $sql = $this->getCreateTableSql($table); |
|
446 | 1 | $uniqueIndexes = []; |
|
447 | |||
448 | 1 | $regexp = '/UNIQUE KEY\s+([^\(\s]+)\s*\(([^\(\)]+)\)/mi'; |
|
449 | 1 | if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) { |
|
450 | 1 | foreach ($matches as $match) { |
|
451 | 1 | $indexName = str_replace('`', '', $match[1]); |
|
452 | 1 | $indexColumns = array_map('trim', explode(',', str_replace('`', '', $match[2]))); |
|
453 | 1 | $uniqueIndexes[$indexName] = $indexColumns; |
|
454 | } |
||
455 | } |
||
456 | |||
457 | 1 | return $uniqueIndexes; |
|
458 | } |
||
459 | |||
460 | /** |
||
461 | * @inheritdoc |
||
462 | */ |
||
463 | 9 | public function createColumnSchemaBuilder($type, $length = null) |
|
467 | |||
468 | /** |
||
469 | * @return bool whether the version of the MySQL being used is older than 5.1. |
||
470 | * @throws InvalidConfigException |
||
471 | * @throws Exception |
||
472 | * @since 2.0.13 |
||
473 | */ |
||
474 | protected function isOldMysql() |
||
482 | |||
483 | /** |
||
484 | * Loads multiple types of constraints and returns the specified ones. |
||
485 | * @param string $tableName table name. |
||
486 | * @param string $returnType return type: |
||
487 | * - primaryKey |
||
488 | * - foreignKeys |
||
489 | * - uniques |
||
490 | * @return mixed constraints. |
||
491 | */ |
||
492 | 30 | private function loadTableConstraints($tableName, $returnType) |
|
562 | } |
||
563 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.