Completed
Push — master ( 7ea508...9f79cc )
by Sébastien
08:16
created

MysqlInformationSchema::getObjectConfig()   F

Complexity

Conditions 30
Paths 19456

Size

Total Lines 237
Code Lines 126

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 134
CRAP Score 30.0029

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 237
ccs 134
cts 136
cp 0.9853
rs 2
cc 30
eloc 126
nc 19456
nop 2
crap 30.0029

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace Soluble\Schema\Source\Mysql;
3
4
use Soluble\Schema\Exception;
5
use Soluble\Schema\Source;
6
use Soluble\Schema\Db\Wrapper\MysqlConnectionAdapter;
7
use Zend\Config\Config;
8
9
class MysqlInformationSchema extends Source\AbstractSource
10
{
11
    /**
12
     * Schema name
13
     *
14
     * @var string
15
     */
16
    protected $schema;
17
18
    /**
19
     * @var MysqlConnectionAdapter
20
     */
21
    protected $adapter;
22
23
24
25
    /**
26
     * Used to restore innodb stats mysql global variable
27
     * @var string
28
     */
29
    protected $mysql_innodbstats_value;
30
31
    /**
32
     * Whether to include full schema options like comment, collations...
33
     * @var boolean
34
     */
35
    protected $include_options = true;
36
    
37
    /**
38
     *
39
     * @var array
40
     */
41
    protected static $localCache = array();
42
43
44
    /**
45
     *
46
     * @var boolean
47
     */
48
    protected $useLocalCaching = true;
49
50
    /**
51
     *
52
     * @var array
53
     */
54
    protected static $fullyCachedSchemas = array();
55
56
57
    /**
58
     * Constructor
59
     * 
60
     * @param \PDO|\mysqli $connection
61
     * @param string $schema default schema, taken from adapter if not given
62
     * @throws Exception\InvalidArgumentException for invalid connection
63
     * @throws Exception\InvalidUsageException thrown if no schema can be found.
64
     */
65 21
    public function __construct($connection, $schema = null)
66
    {
67
        try {
68 21
            $this->adapter = new MysqlConnectionAdapter($connection);
69 21
        } catch (Exception\InvalidArgumentException $e) {
70
            $msg = "MysqlInformationSchema requires a valid 'mysqli' or 'pdo:mysql' connection object ({$e->getMessage()}).";
71
            throw new Exception\InvalidArgumentException($msg);
72
        }
73
74 21
        if ($schema === null) {
75 21
            $schema = $this->adapter->getCurrentSchema();
76 21
            if ($schema === false || $schema == '') {
77
                $msg = "Database name (schema) parameter missing and no default schema set on connection";
78
                throw new Exception\InvalidUsageException($msg);
79
            }
80 21
        }
81
82 21
        $this->setDefaultSchema($schema);
83 21
    }
84
85
86
    /**
87
     * {@inheritdoc}
88
     */
89 1
    public function getUniqueKeys($table, $include_primary = false)
90
    {
91 1
        $this->loadCacheInformation($table);
92 1
        $uniques = (array) self::$localCache[$this->schema]['tables'][$table]['unique_keys'];
93 1
        if ($include_primary) {
94
            try {
95 1
                $pks = $this->getPrimaryKeys($table);
96 1
                if (count($pks) > 0) {
97 1
                    $uniques = array_merge($uniques, array('PRIMARY' => $pks));
98 1
                }
99 1
            } catch (Exception\NoPrimaryKeyException $e) {
100
                // Ignore exception
101
            }
102 1
        }
103 1
        return $uniques;
104
    }
105
106
107
    /**
108
     * {@inheritdoc}
109
     */
110
    public function getIndexesInformation($table)
111
    {
112
        $this->loadCacheInformation($table);
113
        return self::$localCache[$this->schema]['tables'][$table]['indexes'];
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119 5
    public function getPrimaryKey($table)
120
    {
121 5
        $pks = $this->getPrimaryKeys($table);
122 2
        if (count($pks) > 1) {
123 1
            $keys = join(',', $pks);
124 1
            throw new Exception\MultiplePrimaryKeyException(__METHOD__ . ". Multiple primary keys found on table '{$this->schema}'.'$table':  $keys");
125
        }
126 1
        return $pks[0];
127
    }
128
129
130
    /**
131
     * {@inheritdoc}
132
     */
133 9
    public function getPrimaryKeys($table)
134
    {
135 9
        $this->loadCacheInformation($table);
136 6
        $pks = self::$localCache[$this->schema]['tables'][$table]['primary_keys'];
137 6
        if (count($pks) == 0) {
138 3
            throw new Exception\NoPrimaryKeyException(__METHOD__ . ". No primary keys found on table  '{$this->schema}'.'$table'.");
139
        }
140 4
        return $pks;
141
    }
142
143
144
    /**
145
     * {@inheritdoc}
146
     */
147 2
    public function getColumnsInformation($table)
148
    {
149 2
        $this->loadCacheInformation($table);
150 2
        return self::$localCache[$this->schema]['tables'][$table]['columns'];
151
    }
152
153
154
    /**
155
     * {@inheritdoc}
156
     */
157 1
    public function getRelations($table)
158
    {
159 1
        $this->loadCacheInformation($table);
160 1
        return self::$localCache[$this->schema]['tables'][$table]['foreign_keys'];
161
    }
162
163
    /**
164
     * {@inheritdoc}
165
     */
166 3
    public function getTablesInformation()
167
    {
168 3
        $this->loadCacheInformation(null);
169 3
        return self::$localCache[$this->schema]['tables'];
170
    }
171
172
    /**
173
     * Get a table configuration
174
     *
175
     * @throws Exception\ErrorException
176
     * @throws Exception\TableNotFoundException
177
     *
178
     * @param string $table table name
179
     * @param boolean|null $include_options include extended information
180
     * @return array
181
     */
182 11
    protected function getTableConfig($table, $include_options = null)
183
    {
184 11
        if ($include_options === null) {
185 11
            $include_options = $this->include_options;
186 11
        }
187
        
188 11
        $schema = $this->schema;
189
190 11
        if ($this->useLocalCaching &&
191 11
                isset(self::$localCache[$schema]['tables'][$table])) {
192 7
            return self::$localCache[$schema]['tables'][$table];
193
        }
194
195 4
        $config = $this->getObjectConfig($table, $include_options);
196
197 4
        if (!array_key_exists($table, $config['tables'])) {
198 2
            throw new Exception\TableNotFoundException(__METHOD__ . ". Table '$table' in database schema '{$schema}' not found.");
199
        }
200
201 2
        if ($this->useLocalCaching) {
202 2
            if (!array_key_exists($schema, self::$localCache)) {
203 1
                self::$localCache[$schema] = array();
204 1
            }
205 2
            self::$localCache[$schema] = array_merge_recursive(self::$localCache[$schema], $config);
206 2
        }
207
208 2
        return $config['tables'][$table];
209
    }
210
211
212
    /**
213
     * Get schema configuration
214
     *
215
     * @throws Exception\ErrorException
216
     * @throws Exception\SchemaNotFoundException
217
     *
218
     * @param boolean|null $include_options include extended information
219
     * @return array
220
     */
221 2
    protected function getSchemaConfig($include_options = null)
222
    {
223 2
        if ($include_options === null) {
224 2
            $include_options = $this->include_options;
225 2
        }
226 2
        $schema = $this->schema;
227 2
        if ($this->useLocalCaching && in_array($schema, self::$fullyCachedSchemas)) {
228
            return self::$localCache[$schema];
229
        }
230
231 2
        $config = $this->getObjectConfig($table = null, $include_options);
232 2
        if (count($config['tables']) == 0) {
233 1
            throw new Exception\SchemaNotFoundException(__METHOD__ . " Error: schema '{$schema}' not found or without any table or view");
234
        }
235 1
        if ($this->useLocalCaching) {
236 1
            self::$localCache[$schema] = $config;
237 1
            self::$fullyCachedSchemas[] = $schema;
238 1
        }
239 1
        return $config;
240
    }
241
242
    /**
243
     * Return object (table/schema) configuration
244
     *
245
     * @throws Exception\ErrorException
246
     *
247
     * @param string $table
248
     * @param boolean|null $include_options
249
     * @return array
250
     */
251 6
    protected function getObjectConfig($table = null, $include_options = null)
252
    {
253 6
        if ($include_options === null) {
254
            $include_options = $this->include_options;
255
        }
256
        
257 6
        $schema = $this->schema;
258 6
        $qSchema = $this->adapter->quoteValue($schema);
259
260 6
        if ($table !== null) {
261 4
            $qTable = $this->adapter->quoteValue($table);
262 4
            $table_clause = "and (t.TABLE_NAME = $qTable or (kcu.referenced_table_name = $qTable and kcu.constraint_name = 'FOREIGN KEY'))";
263 4
            $table_join_condition = "(t.table_name = kcu.table_name or  kcu.referenced_table_name = t.table_name)";
264 4
        } else {
265 2
            $table_join_condition = "t.table_name = kcu.table_name";
266 2
            $table_clause = '';
267
        }
268
269
        $query = "
270
271
            SELECT
272
                    t.table_name,
273
                    c.column_name,
274
                    c.data_type,
275
                    c.column_type,
276
277
                    c.extra,
278
279
                    tc.constraint_type,
280
                    kcu.constraint_name,
281
                    kcu.referenced_table_name,
282
                    kcu.referenced_column_name,
283
284
                    c.column_default,
285
                    c.is_nullable,
286
                    c.numeric_precision,
287
                    c.numeric_scale,
288
                    c.character_octet_length,
289
                    c.character_maximum_length,
290
                    c.ordinal_position,
291
292
                    c.column_key, -- UNI/MUL/PRI
293
                    c.character_set_name,
294
295
296
                    c.collation_name,
297
298
                    c.column_comment,
299
300
                    t.table_type,
301
                    t.engine,
302
                    t.table_comment,
303
                    t.table_collation
304
305
            FROM `INFORMATION_SCHEMA`.`COLUMNS` c
306
            INNER JOIN `INFORMATION_SCHEMA`.`TABLES` t on c.TABLE_NAME = t.TABLE_NAME
307
            LEFT JOIN `INFORMATION_SCHEMA`.`KEY_COLUMN_USAGE` kcu
308
               on (
309
                    $table_join_condition
310
                     and kcu.table_schema = t.table_schema
311
                     and kcu.column_name = c.column_name
312
                 )
313
              LEFT JOIN
314
                `INFORMATION_SCHEMA`.`TABLE_CONSTRAINTS` tc
315
               on (
316
                     t.table_name = tc.table_name
317
                      and tc.table_schema = t.table_schema
318
                      and tc.constraint_name = kcu.constraint_name
319
                  )
320
321
322 6
            where c.TABLE_SCHEMA = $qSchema
323 6
            and t.TABLE_SCHEMA = $qSchema
324 6
            $table_clause
325 6
            and (kcu.table_schema = $qSchema  or kcu.table_schema is null)
326
327
            and (kcu.column_name = c.column_name or kcu.column_name is null)
328
            order by t.table_name, c.ordinal_position
329 6
        ";
330
331 6
        $this->disableInnoDbStats();
332
        try {
333 6
            $results = $this->adapter->query($query);
334 6
        } catch (\Exception $e) {
335
            //@codeCoverageIgnoreStart
336
            $this->restoreInnoDbStats();
337
            throw new Exception\ErrorException(__METHOD__ . ": " . $e->getMessage());
338
            //@codeCoverageIgnoreEnd
339
        }
340 6
        $this->restoreInnoDbStats();
341
342 6
        $references = array();
343 6
        $config = new Config(array('tables' => array()), true);
344 6
        $tables = $config->offsetGet('tables');
345
346
347 6
        foreach ($results as $r) {
348
            // Setting table information
349 3
            $table_name = $r['table_name'];
350 3
            if (!$tables->offsetExists($table_name)) {
351
                $table_def = array(
352 3
                    'name'          => $table_name,
353 3
                    'columns'       => array(),
354 3
                    'primary_keys'  => array(),
355 3
                    'unique_keys'   => array(),
356 3
                    'foreign_keys'  => array(),
357 3
                    'references'    => array(),
358 3
                    'indexes'       => array(),
359 3
                );
360 3
                if ($include_options) {
361 3
                    $table_def['options'] = array(
362 3
                       'comment'   => $r['table_comment'],
363 3
                       'collation' => $r['table_collation'],
364 3
                       'type'      => $r['table_type'],
365 3
                       'engine'    => $r['engine']
366 3
                    );
367 3
                }
368 3
                $tables->offsetSet($table_name, $table_def);
369 3
            }
370 3
            $table   = $tables->offsetGet($table_name);
371 3
            $columns = $table->columns;
372 3
            $column_name = $r['column_name'];
373
374 3
            $data_type = strtolower($r['data_type']);
375
376
            $col_def = array(
377 3
                'type'          => $data_type,
378 3
                'primary'       => ($r['constraint_type'] == 'PRIMARY KEY'),
379 3
                'nullable'      => ($r['is_nullable'] == 'YES'),
380 3
                'default'       => $r['column_default']
381 3
            );
382 3
            if (($r['constraint_type'] == 'PRIMARY KEY')) {
383 2
                $col_def['primary'] = true;
384 2
                $col_def['autoincrement'] = ($r['extra'] == 'auto_increment');
385 2
            }
386
387 3
            $has_charset = false;
388 3
            if (in_array($data_type, array('int', 'tinyint', 'mediumint', 'bigint', 'int', 'smallint', 'year'))) {
389 3
                $col_def['unsigned']  = (bool) preg_match('/unsigned/', strtolower($r['column_type']));
390 3
                $col_def['precision'] = is_numeric($r['numeric_precision']) ? (int) $r['numeric_precision'] : null;
391 3
            } elseif (in_array($data_type, array('real', 'double precision', 'decimal', 'numeric', 'float', 'dec', 'fixed'))) {
392 1
                $col_def['precision'] = is_numeric($r['numeric_precision']) ? (int) $r['numeric_precision'] : null;
393 1
                $col_def['scale']     = is_numeric($r['numeric_scale']) ? (int) $r['numeric_scale'] : null;
394 2
            } elseif (in_array($data_type, array('timestamp', 'date', 'time', 'datetime'))) {
395
                // nothing yet
396 2
            } elseif (in_array($data_type, array('char', 'varchar', 'binary', 'varbinary', 'text', 'tinytext', 'mediumtext', 'longtext'))) {
397 2
                $col_def['octet_length'] = is_numeric($r['character_octet_length']) ? (int) $r['character_octet_length'] : null;
398 2
                $col_def['length'] = is_numeric($r['character_maximum_length']) ? (int) $r['character_maximum_length'] : null;
399 2
                $has_charset = true;
400 2
            } elseif (in_array($data_type, array('blob', 'tinyblob', 'mediumblob', 'longblob'))) {
401 1
                $col_def['octet_length'] = (int) $r['character_octet_length'];
402 1
                $col_def['length'] = (int) $r['character_maximum_length'];
403 1
            } elseif (in_array($data_type, array('enum', 'set'))) {
404 1
                $col_def['octet_length'] = (int) $r['character_octet_length'];
405 1
                $col_def['length'] = (int) $r['character_maximum_length'];
406 1
                $def = $r['column_type'];
407
408 1
                preg_match_all("/'([^']+)'/", $def, $matches);
409 1
                if (is_array($matches[1]) && count($matches) > 0) {
410 1
                    $col_def['values'] = $matches[1];
411 1
                }
412 1
            }
413
414 3
            if ($include_options) {
415 3
                $col_def['options'] = array(
416 3
                        'comment'           => $r['column_comment'],
417 3
                        'definition'        => $r['column_type'],
418 3
                        'column_key'        => $r['column_key'],
419 3
                        'ordinal_position'  => $r['ordinal_position'],
420 3
                        'constraint_type'   => $r['constraint_type'], // 'PRIMARY KEY', 'FOREIGN_KEY', 'UNIQUE'
421
                    );
422 3
                if ($has_charset) {
423 2
                    $col_def['options']['charset']     = $r['character_set_name'];
424 2
                    $col_def['options']['collation']   = $r['collation_name'];
425 2
                }
426 3
            }
427
428 3
            $columns[$column_name] = $col_def;
429
430 3
            $foreign_keys = $table->foreign_keys;
431 3
            $unique_keys  = $table->unique_keys;
432
433 3
            $constraint_name = $r['constraint_name'];
434 3
            $referenced_table_name = $r['referenced_table_name'];
435 3
            $referenced_column_name = $r['referenced_column_name'];
436 3
            switch ($r['constraint_type']) {
437 3
                case 'PRIMARY KEY':
438 2
                    $table->primary_keys = array_merge($table->primary_keys->toArray(), (array) $column_name);
439 2
                    break;
440 3
                case 'UNIQUE':
441 2
                    if (!$unique_keys->offsetExists($constraint_name)) {
442 2
                        $unique_keys[$constraint_name] = array();
443 2
                    }
444 2
                    $unique_keys[$constraint_name] = array_merge($unique_keys[$constraint_name]->toArray(), (array) $column_name);
445 2
                    break;
446 3
                case 'FOREIGN KEY':
447
                    /*
448
                    if (!$foreign_keys->offsetExists($constraint_name)) {
449
                        $foreign_keys[$constraint_name] = array();
450
                    }
451
                     *
452
                     */
453
                    $fk = array(
454 1
                       'referenced_table'  => $referenced_table_name,
455 1
                       'referenced_column' => $referenced_column_name,
456
                       'constraint_name' => $constraint_name
457 1
                    );
458 1
                    $foreign_keys[$column_name] = $fk;
459
                    //$table->references[$referenced_table_name] = array($column_name => $r['referenced_column_name']);
460
461 1
                    if (!array_key_exists($referenced_table_name, $references)) {
462 1
                        $references[$referenced_table_name] = array();
463 1
                    }
464
465 1
                    $references[$referenced_table_name][] = array(
466 1
                        'column' => $column_name,
467
                        //'referenced_table' => $table_name,
468 1
                        'referenced_column' => $referenced_column_name,
469
                        'constraint_name' => $constraint_name
470 1
                    );
471 1
                    break;
472 3
            }
473 6
        }
474
475 6
        foreach ($references as $referenced_table_name => $refs) {
476 1
            if ($tables->offsetExists($referenced_table_name)) {
477 1
                $table = $tables[$referenced_table_name];
478 1
                $references = $table->references;
479 1
                $references[$referenced_table_name] = $refs;
480 1
            }
481 6
        }
482
483 6
        $array = $config->toArray();
484 6
        unset($config);
485 6
        return $array;
486
487
    }
488
489
    /**
490
     * Disable innodbstats will increase speed of metadata lookups
491
     *
492
     * @return void
493
     */
494 6
    protected function disableInnoDbStats()
495
    {
496 6
        $sql = "show global variables like 'innodb_stats_on_metadata'";
497
        try {
498 6
            $results = $this->adapter->query($sql);
499 6
            if (count($results) > 0) {
500 6
                $row = $results->offsetGet(0);
501
502 6
                $value = strtoupper($row['Value']);
503
                // if 'on' no need to do anything
504 6
                if ($value != 'OFF') {
505 6
                    $this->mysql_innodbstats_value = $value;
506
                    // disabling innodb_stats
507 6
                    $this->adapter->execute("set global innodb_stats_on_metadata='OFF'");
508 6
                }
509 6
            }
510 6
        } catch (\Exception $e) {
511
            // do nothing, silently fallback
512
        }
513 6
    }
514
515
516
    /**
517
     * Restore old innodbstats variable
518
     * @return void
519
     */
520 6
    protected function restoreInnoDbStats()
521
    {
522 6
        $value = $this->mysql_innodbstats_value;
523 6
        if ($value !== null) {
524
            // restoring old variable
525 6
            $this->adapter->execute("set global innodb_stats_on_metadata='$value'");
526 6
        }
527 6
    }
528
529
530
    /**
531
     *
532
     * @param string $table
533
     * @throws Exception\InvalidArgumentException
534
     * @throws Exception\TableNotFoundException
535
     *
536
     */
537 15
    protected function loadCacheInformation($table = null)
538
    {
539 15
        $schema = $this->schema;
540 15
        $this->checkTableArgument($table);
541
542 13
        if (!in_array($schema, self::$fullyCachedSchemas)) {
543 3
            if ($table !== null) {
544 3
                $this->getTableConfig($table);
545 3
            } else {
546
                $this->getSchemaConfig();
547
            }
548 13
        } elseif ($table !== null) {
549
            // Just in case to check if table exists
550 7
            $this->getTableConfig($table);
551 6
        }
552 12
    }
553
554
    /**
555
     * Clear local cache information for the current schema
556
     *
557
     * @throws Exception\InvalidArgumentException
558
     */
559 1
    public function clearCacheInformation()
560
    {
561 1
        $schema = $this->schema;
562 1
        if (array_key_exists($schema, self::$localCache)) {
563 1
            unset(self::$localCache[$schema]);
564 1
            if (($key = array_search($schema, self::$fullyCachedSchemas)) !== false) {
565 1
                unset(self::$fullyCachedSchemas[$key]);
566 1
            }
567 1
        }
568 1
    }
569
}
570