GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Issues (23)

src/Extractor.php (2 issues)

1
<?php
2
3
namespace Graze\Morphism;
4
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\DBALException;
7
use Graze\Morphism\Parse\Token;
8
use PDO;
9
10
/**
11
 * Fast database schema extractor - instead of using mysqldump, it talks
12
 * directly to the INFORMATION_SCHEMA, resulting in a 2x speedup.
13
 * phpcs:ignoreFile Squiz.NamingConventions.ValidVariableName
14
 */
15
class Extractor
16
{
17
    /** @var Connection */
18
    private $dbh = null;
19
    /** @var array */
20
    private $databases = null;
21
    /** @var bool */
22
    private $createDatabase = true;
23
    /** @var bool */
24
    private $quoteNames = true;
25
26
    /**
27
     * Constructor
28
     * @param Connection$dbh
29
     */
30
    public function __construct(Connection $dbh)
31
    {
32
        $this->dbh = $dbh;
33
    }
34
35
    /**
36
     * If $flag is true, the extractor will output CREATE DATABASE and USE
37
     * statements. Defaults to true.
38
     *
39
     * @param bool $flag
40
     */
41
    public function setCreateDatabases($flag)
42
    {
43
        $this->createDatabase = (bool) $flag;
44
    }
45
46
    /**
47
     * Specifies the database schemas to extract. All non-system schemas
48
     * (i.e. not MYSQL or INFORMATION_SCHEMA) will be extracted if none
49
     * are specified.
50
     *
51
     * @param string[] $databases
52
     */
53
    public function setDatabases(array $databases)
54
    {
55
        $this->databases = $databases;
56
    }
57
58
    /**
59
     * If $flags is true, the extractor will enclose names of database objects
60
     * (schemas, tables, columns, etc) in backquotes. Defaults to true.
61
     *
62
     * @param bool $flag
63
     */
64
    public function setQuoteNames($flag)
65
    {
66
        $this->quoteNames = (bool) $flag;
67
    }
68
69
    /**
70
     * Prepare, bind, and execute the specified query against the current
71
     * connection, and return the result set as an array of objects.
72
     *
73
     * @param string $sql
74
     * @param mixed[] $binds
75
     * @return array result-row-object[]
76
     * @throws DBALException
77
     */
78
    private function query($sql, array $binds = [])
79
    {
80
        $sth = $this->dbh->prepare($sql);
81
        $sth->execute($binds);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Statement::execute() has been deprecated: Statement::execute() is deprecated, use Statement::executeQuery() or executeStatement() instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

81
        /** @scrutinizer ignore-deprecated */ $sth->execute($binds);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
82
        $rows = $sth->fetchAll(PDO::FETCH_OBJ);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Statement::fetchAll() has been deprecated: Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

82
        $rows = /** @scrutinizer ignore-deprecated */ $sth->fetchAll(PDO::FETCH_OBJ);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
83
        return $rows;
84
    }
85
86
    /**
87
     * Returns $count comma-separated placeholders (e.g. "?", "?,?", etc)
88
     * or "NULL" if $count is zero, suitable for use with an IN() clause in
89
     * a prepared query.
90
     *
91
     * @param int|array $count
92
     * @return string
93
     */
94
    private static function placeholders($count)
95
    {
96
        if (is_array($count)) {
97
            $count = count($count);
98
        }
99
        if ($count == 0) {
100
            return "NULL";
101
        }
102
        return implode(',', array_fill(0, $count, '?'));
103
    }
104
105
    /**
106
     * @return array [$schema => SCHEMATA-object, ...]
107
     * @throws DBALException
108
     */
109
    private function getSchemata()
110
    {
111
        if (count($this->databases) == 0) {
112
            $rows = $this->query("
113
                SELECT *
114
                FROM INFORMATION_SCHEMA.SCHEMATA
115
                WHERE SCHEMA_NAME NOT IN ('mysql', 'information_schema')
116
            ");
117
        } else {
118
            $binds = $this->databases;
119
            $placeholders = self::placeholders($binds);
120
            $rows = $this->query(
121
                "
122
                SELECT *
123
                FROM INFORMATION_SCHEMA.SCHEMATA
124
                WHERE SCHEMA_NAME IN ($placeholders)
125
                ",
126
                $binds
127
            );
128
        }
129
130
        $schemata = [];
131
        foreach ($rows as $schema) {
132
            $schemata[$schema->SCHEMA_NAME] = $schema;
133
        }
134
        return $schemata;
135
    }
136
137
    /**
138
     * @param array $databases
139
     * @return array [$schema => [$table => TABLES-object, ...], ...]
140
     * @throws DBALException
141
     */
142
    private function getTables(array $databases)
143
    {
144
        $placeholders = self::placeholders($databases);
145
        $rows = $this->query(
146
            "
147
            SELECT *
148
            FROM INFORMATION_SCHEMA.TABLES
149
            WHERE
150
                TABLE_SCHEMA IN ($placeholders) AND
151
                TABLE_TYPE = 'BASE TABLE'
152
            ORDER BY
153
                TABLE_SCHEMA,
154
                TABLE_NAME
155
            ",
156
            $databases
157
        );
158
        $tables = [];
159
        foreach ($rows as $table) {
160
            $tables[$table->TABLE_SCHEMA][$table->TABLE_NAME] = $table;
161
        }
162
        return $tables;
163
    }
164
165
    /**
166
     * @param string[] $databases
167
     * @return array [$schema => [$table => [COLUMNS-object, ...], ...], ...]
168
     * @throws DBALException
169
     */
170
    private function getColumns(array $databases)
171
    {
172
        $placeholders = self::placeholders($databases);
173
        $rows = $this->query(
174
            "
175
            SELECT *
176
            FROM INFORMATION_SCHEMA.COLUMNS
177
            WHERE TABLE_SCHEMA IN ($placeholders)
178
            ORDER BY
179
                TABLE_SCHEMA,
180
                TABLE_NAME,
181
                ORDINAL_POSITION
182
            ",
183
            $databases
184
        );
185
        $columns = [];
186
        foreach ($rows as $column) {
187
            $columns[$column->TABLE_SCHEMA][$column->TABLE_NAME][$column->ORDINAL_POSITION] = $column;
188
        }
189
        return $columns;
190
    }
191
192
    /**
193
     * @param string[] $databases
194
     * @return array [$schema => [$table => [$index => [STATISTICS-object, ...], ...], ...], ...]
195
     * @throws DBALException
196
     */
197
    private function getKeys(array $databases)
198
    {
199
        $placeholders = self::placeholders($databases);
200
        $rows = $this->query(
201
            "
202
            SELECT *
203
            FROM INFORMATION_SCHEMA.STATISTICS
204
            WHERE TABLE_SCHEMA IN ($placeholders)
205
            ORDER BY
206
                TABLE_SCHEMA,
207
                TABLE_NAME,
208
                INDEX_NAME = 'PRIMARY' DESC,
209
                INDEX_NAME,
210
                SEQ_IN_INDEX
211
            ",
212
            $databases
213
        );
214
        $keys = [];
215
        foreach ($rows as $key) {
216
            $keys[$key->TABLE_SCHEMA][$key->TABLE_NAME][$key->INDEX_NAME][] = $key;
217
        }
218
        return $keys;
219
    }
220
221
    /**
222
     * @param string[] $databases
223
     * @return array [$schema => [$table => [$constraint => [KEY_COLUMN_USAGE-object, ...], ...], ...], ...]
224
     * @throws DBALException
225
     */
226
    private function getReferences(array $databases)
227
    {
228
        $placeholders = self::placeholders($databases);
229
        $rows = $this->query(
230
            "
231
            SELECT *
232
            FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
233
            WHERE TABLE_SCHEMA IN ($placeholders)
234
            AND REFERENCED_TABLE_SCHEMA IS NOT NULL
235
            ORDER BY
236
                TABLE_SCHEMA,
237
                TABLE_NAME,
238
                CONSTRAINT_NAME,
239
                ORDINAL_POSITION
240
            ",
241
            $databases
242
        );
243
        $references = [];
244
        foreach ($rows as $reference) {
245
            $references[$reference->TABLE_SCHEMA][$reference->TABLE_NAME][$reference->CONSTRAINT_NAME][] = $reference;
246
        }
247
        return $references;
248
    }
249
250
    /**
251
     * @param array $databases
252
     * @return array
253
     * @throws DBALException
254
     */
255
    private function getConstraints(array $databases)
256
    {
257
        $placeholders = self::placeholders($databases);
258
        $rows = $this->query(
259
            "
260
            SELECT *
261
            FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
262
            WHERE CONSTRAINT_SCHEMA IN ($placeholders)
263
            ORDER BY
264
                CONSTRAINT_SCHEMA,
265
                TABLE_NAME,
266
                CONSTRAINT_NAME
267
            ",
268
            $databases
269
        );
270
        $constraints = [];
271
        foreach ($rows as $constraint) {
272
            $constraints[$constraint->CONSTRAINT_SCHEMA][$constraint->TABLE_NAME][$constraint->CONSTRAINT_NAME] = $constraint;
273
        }
274
        return $constraints;
275
    }
276
277
    /**
278
     * Returns an array of SQL DDL statements to create the specified database.
279
     *
280
     * @param mixed $schema SCHEMA-object
281
     * @return string[]
282
     */
283
    private function getCreateDatabase($schema)
284
    {
285
        $escapedDatabase = Token::escapeIdentifier($schema->SCHEMA_NAME);
286
        return [
287
            "CREATE DATABASE $escapedDatabase" .
288
            " DEFAULT CHARACTER SET $schema->DEFAULT_CHARACTER_SET_NAME" .
289
            " COLLATE $schema->DEFAULT_COLLATION_NAME",
290
        ];
291
    }
292
293
    /**
294
     * Returns an array of SQL statements to select the specified database
295
     * as the default for the connection.
296
     *
297
     * @param mixed $schema SCHEMA-object
298
     * @return string[]
299
     */
300
    private function getUseDatabase($schema)
301
    {
302
        $escapedDatabase = Token::escapeIdentifier($schema->SCHEMA_NAME);
303
        return [
304
            "USE $escapedDatabase",
305
        ];
306
    }
307
308
    /**
309
     * Returns an array of clauses that will form part of a CREATE TABLE statement
310
     * to create the specified columns.
311
     *
312
     * @param array $columns COLUMNS-object[]
313
     * @return string[]
314
     */
315
    private function getColumnDefs(array $columns)
316
    {
317
        $defColumns = [];
318
        foreach ($columns as $column) {
319
            $defColumn = "$column->COLUMN_NAME";
320
            $defColumn .= " $column->COLUMN_TYPE";
321
            if (!is_null($column->CHARACTER_SET_NAME)) {
322
                $defColumn .= " CHARACTER SET $column->CHARACTER_SET_NAME";
323
            }
324
            if (!is_null($column->COLLATION_NAME)) {
325
                $defColumn .= " COLLATE $column->COLLATION_NAME";
326
            }
327
            if ($column->IS_NULLABLE == 'NO') {
328
                $defColumn .= " NOT NULL";
329
            } else {
330
                $defColumn .= " NULL";
331
            }
332
            // Maria DB defines null default columns as string 'NULL' rather than null, these need to be ignored.
333
            if (!is_null($column->COLUMN_DEFAULT) && !($column->IS_NULLABLE == 'YES' && $column->COLUMN_DEFAULT == 'NULL')) {
334
                if (in_array($column->DATA_TYPE, ['timestamp', 'datetime']) &&
335
                    $column->COLUMN_DEFAULT == 'CURRENT_TIMESTAMP'
336
                ) {
337
                    $defColumn .= " DEFAULT $column->COLUMN_DEFAULT";
338
                } else {
339
                    $defColumn .= " DEFAULT " . Token::escapeString($column->COLUMN_DEFAULT);
340
                }
341
            }
342
            if ($column->EXTRA != '') {
343
                $defColumn .= " $column->EXTRA";
344
            }
345
            if ($column->COLUMN_COMMENT != '') {
346
                $defColumn .= " COMMENT " . Token::escapeString($column->COLUMN_COMMENT);
347
            }
348
            $defColumns[] = "  $defColumn";
349
        }
350
        return $defColumns;
351
    }
352
353
    /**
354
     * Returns an array of clauses that will form part of a CREATE TABLE statement
355
     * to create the specified indexes.
356
     *
357
     * @param array $keys [$index => STATISTICS-object[]]
358
     * @return string[]
359
     */
360
    private function getKeyDefs(array $keys)
361
    {
362
        $defKeys = [];
363
        foreach ($keys as $key) {
364
            $firstKeyPart = $key[0];
365
            if ($firstKeyPart->INDEX_NAME == 'PRIMARY') {
366
                $defKey = 'PRIMARY KEY';
367
            } else {
368
                $escapedIndexName = Token::escapeIdentifier($firstKeyPart->INDEX_NAME);
369
                if ($firstKeyPart->INDEX_TYPE == 'FULLTEXT') {
370
                    $defKey = "FULLTEXT $escapedIndexName";
371
                } elseif ($firstKeyPart->NON_UNIQUE) {
372
                    $defKey = "KEY $escapedIndexName";
373
                } else {
374
                    $defKey = "UNIQUE KEY $escapedIndexName";
375
                }
376
            }
377
            $defKeyParts = [];
378
            foreach ($key as $keyPart) {
379
                $defKeyPart = $keyPart->COLUMN_NAME;
380
                if (!is_null($keyPart->SUB_PART)) {
381
                    $defKeyPart .= "($keyPart->SUB_PART)";
382
                }
383
                $defKeyParts[] = $defKeyPart;
384
            }
385
            $defKeys[] = "  $defKey (" . implode(',', $defKeyParts) . ")";
386
        }
387
        return $defKeys;
388
    }
389
390
    /**
391
     * Returns an array of clauses that will form part of a CREATE TABLE statement
392
     * to create the specified foreign key constraints.
393
     *
394
     * @param array $references [$constraint => [KEY_COLUMN_USAGE-object, ...], ...]
395
     * @param array $constraints [$constraint => REFERENTIAL_CONSTRAINTS-object, ...]
396
     * @return string[]
397
     */
398
    private function getReferenceDefs(array $references, array $constraints)
399
    {
400
        $defReferences = [];
401
        foreach ($references as $reference) {
402
            $firstRefPart = $reference[0];
403
            $constraintName = Token::escapeIdentifier($firstRefPart->CONSTRAINT_NAME);
404
            $referencedTable = Token::escapeIdentifier($firstRefPart->REFERENCED_TABLE_NAME);
405
            if ($firstRefPart->REFERENCED_TABLE_SCHEMA != $firstRefPart->TABLE_SCHEMA) {
406
                $referencedTable = Token::escapeIdentifier($firstRefPart->REFERENCED_TABLE_SCHEMA) . '.' . $referencedTable;
407
            }
408
            $defForeignParts = [];
409
            $defReferenceParts = [];
410
            foreach ($reference as $referencePart) {
411
                $defForeignParts[] = Token::escapeIdentifier($referencePart->COLUMN_NAME);
412
                $defReferenceParts[] = Token::escapeIdentifier($referencePart->REFERENCED_COLUMN_NAME);
413
            }
414
            $constraint = $constraints[$firstRefPart->CONSTRAINT_NAME];
415
            $options = "";
416
            if ($constraint->MATCH_OPTION != 'NONE') {
417
                $options = " MATCH " . $constraint->MATCH_OPTION;
418
            }
419
            if ($constraint->UPDATE_RULE != 'RESTRICT') {
420
                $options .= " ON UPDATE " . $constraint->UPDATE_RULE;
421
            }
422
            if ($constraint->DELETE_RULE != 'RESTRICT') {
423
                $options .= " ON DELETE " . $constraint->DELETE_RULE;
424
            }
425
            $defReferences[] =
426
                "  CONSTRAINT $constraintName" .
427
                " FOREIGN KEY (" . implode(',', $defForeignParts) . ")" .
428
                " REFERENCES $referencedTable (" . implode(',', $defReferenceParts) . ")" .
429
                $options;
430
        }
431
        return $defReferences;
432
    }
433
434
    /**
435
     * Returns an array of table options which will form part of the DDL
436
     * necessary to create the specified table.
437
     *
438
     * @param mixed $table TABLES-object[]
439
     * @return string[]
440
     */
441
    private function getTableOptionDefs($table)
442
    {
443
        $defTableOptions = [];
444
        $defTableOptions[] = "ENGINE=$table->ENGINE";
445
        if (strcasecmp($table->ROW_FORMAT, 'COMPACT') != 0) {
446
            $defTableOptions[] = "ROW_FORMAT=$table->ROW_FORMAT";
447
        }
448
        if ($table->AUTO_INCREMENT) {
449
            $defTableOptions[] = "AUTO_INCREMENT=$table->AUTO_INCREMENT";
450
        }
451
        $collate = $table->TABLE_COLLATION;
452
        list($charset) = explode('_', $table->TABLE_COLLATION);
453
        $defTableOptions[] = "DEFAULT CHARSET=$charset COLLATE=$collate";
454
        if ($table->CREATE_OPTIONS != '') {
455
            $defTableOptions[] = $table->CREATE_OPTIONS;
456
        }
457
        if ($table->TABLE_COMMENT != '') {
458
            $defTableOptions[] = "COMMENT " . Token::escapeString($table->TABLE_COMMENT);
459
        }
460
        return $defTableOptions;
461
    }
462
463
    /**
464
     * Returns an array of SQL DDL statements to create the specified table.
465
     *
466
     * @param mixed $table TABLES-object
467
     * @param array $columns COLUMNS-object[]
468
     * @param array $keys [$index => STATISTICS-object[]]
469
     * @param array $references [$constraint => [KEY_COLUMN_USAGE-object, ...], ...]
470
     * @param array $constraints [$constraint => REFERENTIAL_CONSTRAINTS-object, ...]
471
     * @return array
472
     */
473
    private function getCreateTable($table, array $columns, array $keys, array $references, array $constraints)
474
    {
475
        $tableName = $table->TABLE_NAME;
476
477
        $defColumns = $this->getColumnDefs($columns);
478
        $defKeys = $this->getKeyDefs($keys);
479
        $defReferences = $this->getReferenceDefs($references, $constraints);
480
        $defTableOptions = $this->getTableOptionDefs($table);
481
482
        return [
483
            "CREATE TABLE " . Token::escapeIdentifier($tableName) . " (\n" .
484
            implode(",\n", array_merge($defColumns, $defKeys, $defReferences)) . "\n" .
485
            ") " . implode(' ', $defTableOptions),
486
        ];
487
    }
488
489
    /**
490
     * Returns an array of SQL DDL statements extracted from the database
491
     * connection provided at construction.
492
     *
493
     * @return string[]
494
     * @throws DBALException
495
     */
496
    public function extract()
497
    {
498
        Token::setQuoteNames($this->quoteNames);
499
500
        $schemata = $this->getSchemata();
501
        $databases = array_keys($schemata);
502
        $tables = $this->getTables($databases);
503
        $columns = $this->getColumns($databases);
504
        $keys = $this->getKeys($databases);
505
        $references = $this->getReferences($databases);
506
        $constraints = $this->getConstraints($databases);
507
508
        $statements = [];
509
        foreach ($schemata as $database => $schema) {
510
            if ($this->createDatabase) {
511
                $statements = array_merge(
512
                    $statements,
513
                    $this->getCreateDatabase($schema),
514
                    $this->getUseDatabase($schema)
515
                );
516
            }
517
518
            foreach (isset($tables[$database]) ? $tables[$database] : [] as $tableName => $table) {
519
                $tableColumns = $columns[$database][$tableName];
520
                $tableKeys = isset($keys[$database][$tableName]) ? $keys[$database][$tableName] : [];
521
                $tableReferences = isset($references[$database][$tableName]) ? $references[$database][$tableName] : [];
522
                $tableConstraints = isset($constraints[$database][$tableName]) ? $constraints[$database][$tableName] : [];
523
                $statements = array_merge($statements, $this->getCreateTable($table, $tableColumns, $tableKeys, $tableReferences, $tableConstraints));
524
            }
525
        }
526
527
        return $statements;
528
    }
529
}
530