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/Command/Diff.php (4 issues)

1
<?php
2
3
namespace Graze\Morphism\Command;
4
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\DBALException;
7
use Exception;
8
use Graze\Morphism\Parse\TokenStream;
9
use Graze\Morphism\Parse\Token;
10
use Graze\Morphism\Parse\MysqlDump;
11
use Graze\Morphism\Extractor;
12
use Graze\Morphism\Config;
13
use InvalidArgumentException;
14
use RuntimeException;
15
use Symfony\Component\Console\Command\Command;
16
use Symfony\Component\Console\Input\InputArgument;
17
use Symfony\Component\Console\Input\InputInterface;
18
use Symfony\Component\Console\Input\InputOption;
19
use Symfony\Component\Console\Output\OutputInterface;
20
21
class Diff extends Command
22
{
23
    const COMMAND_NAME              = 'diff';
24
25
    // Command line arguments
26
    const ARGUMENT_CONFIG_FILE      = 'config-file';
27
    const ARGUMENT_CONNECTIONS      = 'connections';
28
29
    // Command line options
30
    const OPTION_ENGINE             = 'engine';
31
    const OPTION_COLLATION          = 'collation';
32
    const OPTION_APPLY_CHANGES      = 'apply-changes';
33
    const OPTION_LOG_DIR            = 'log-dir';
34
35
    const OPTION_QUOTE_NAMES        = 'quote-names';
36
    const OPTION_NO_QUOTE_NAMES     = 'no-quote-names';
37
    const OPTION_CREATE_TABLE       = 'create-table';
38
    const OPTION_NO_CREATE_TABLE    = 'no-create-table';
39
    const OPTION_DROP_TABLE         = 'drop-table';
40
    const OPTION_NO_DROP_TABLE      = 'no-drop-table';
41
    const OPTION_ALTER_ENGINE       = 'alter-engine';
42
    const OPTION_NO_ALTER_ENGINE    = 'no-alter-engine';
43
    const OPTION_LOG_SKIPPED        = 'log-skipped';
44
    const OPTION_NO_LOG_SKIPPED     = 'no-log-skipped';
45
    const OPTION_NO_FOREIGN_KEY_CHECKS = 'no-foreign-key-checks';
46
47
    /** @var string */
48
    private $engine = 'InnoDB';
49
    /** @var string|null */
50
    private $collation = null;
51
    /** @var bool */
52
    private $quoteNames = true;
53
    /** @var bool */
54
    private $createTable = true;
55
    /** @var bool */
56
    private $dropTable = true;
57
    /** @var bool */
58
    private $alterEngine = true;
59
    /** @var string|null */
60
    private $configFile = null;
61
    /** @var array */
62
    private $connectionNames = [];
63
    /** @var string */
64
    private $applyChanges = 'no';
65
    /** @var string null */
66
    private $logDir = null;
67
    /** @var bool */
68
    private $logSkipped = true;
69
    /** @var bool */
70
    private $disableForeignKeyChecks = false;
71
72
    protected function configure()
73
    {
74
        $this->setName(self::COMMAND_NAME);
75
76
        $helpText = sprintf(
77
            "Usage: %s [OPTION] CONFIG-FILE [CONN] ...\n" .
78
            "Extracts schema definitions from the named connections, and outputs the\n" .
79
            "necessary ALTER TABLE statements to transform them into what is defined\n" .
80
            "under the schema path. If no connections are specified, all connections\n" .
81
            "in the config with 'morphism: enable: true' will be used.\n" .
82
            "\n" .
83
            "GENERAL OPTIONS:\n" .
84
            "  -h, -help, --help      display this message, and exit\n" .
85
            "  --engine=ENGINE        set the default database engine\n" .
86
            "  --collation=COLLATION  set the default collation\n" .
87
            "  --[no-]quote-names     quote names with `...`; default: yes\n" .
88
            "  --[no-]create-table    output CREATE TABLE statements; default: yes\n" .
89
            "  --[no-]drop-table      output DROP TABLE statements; default: yes\n" .
90
            "  --[no-]alter-engine    output ALTER TABLE ... ENGINE=...; default: yes\n" .
91
            "  --apply-changes=WHEN   apply changes (yes/no/confirm); default: no\n" .
92
            "  --log-dir=DIR          log applied changes to DIR - one log file will be\n" .
93
            "                         created per connection; default: none\n" .
94
            "  --[no-]log-skipped     log skipped queries (commented out); default: yes\n" .
95
            "\n" .
96
            "CONFIG-FILE\n" .
97
            "A YAML file mapping connection names to parameters. See the morphism project's\n" .
98
            "README.md file for detailed information.\n" .
99
            "",
100
            self::COMMAND_NAME
101
        );
102
        $this->setHelp($helpText);
103
104
        $this->setDescription("Show necessary DDL statements to make a given database match the schema files (and optionally apply the changes)");
105
106
        $this->addArgument(
107
            self::ARGUMENT_CONFIG_FILE,
108
            InputArgument::REQUIRED
109
        );
110
111
        $this->addArgument(
112
            self::ARGUMENT_CONNECTIONS,
113
            InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
114
            '',
115
            []
116
        );
117
118
        $this->addOption(
119
            self::OPTION_ENGINE,
120
            null,
121
            InputOption::VALUE_REQUIRED,
122
            'Database engine',
123
            'InnoDB'
124
        );
125
        $this->addOption(
126
            self::OPTION_COLLATION,
127
            null,
128
            InputOption::VALUE_REQUIRED,
129
            'Database collation'
130
        );
131
132
        $this->addOption(self::OPTION_QUOTE_NAMES);
133
        $this->addOption(self::OPTION_NO_QUOTE_NAMES);
134
135
        $this->addOption(self::OPTION_CREATE_TABLE);
136
        $this->addOption(self::OPTION_NO_CREATE_TABLE);
137
138
        $this->addOption(self::OPTION_DROP_TABLE);
139
        $this->addOption(self::OPTION_NO_DROP_TABLE);
140
141
        $this->addOption(self::OPTION_ALTER_ENGINE);
142
        $this->addOption(self::OPTION_NO_ALTER_ENGINE);
143
144
        $this->addOption(
145
            self::OPTION_APPLY_CHANGES,
146
            null,
147
            InputOption::VALUE_REQUIRED,
148
            '',
149
            "no"
150
        );
151
152
        $this->addOption(
153
            self::OPTION_LOG_DIR,
154
            null,
155
            InputOption::VALUE_REQUIRED
156
        );
157
158
        $this->addOption(self::OPTION_LOG_SKIPPED);
159
        $this->addOption(self::OPTION_NO_LOG_SKIPPED);
160
        $this->addOption(self::OPTION_NO_FOREIGN_KEY_CHECKS);
161
    }
162
163
    /**
164
     * @param Connection $connection
165
     * @param string $dbName
166
     * @return MysqlDump
167
     * @throws DBALException
168
     */
169
    private function getCurrentSchema(Connection $connection, $dbName)
170
    {
171
        $extractor = new Extractor($connection);
172
        $extractor->setDatabases([$dbName]);
173
        $extractor->setCreateDatabases(false);
174
        $extractor->setQuoteNames($this->quoteNames);
175
176
        $text = '';
177
        foreach ($extractor->extract() as $query) {
178
            $text .= "$query;\n";
179
        }
180
        $stream = TokenStream::newFromText($text, '');
181
182
        $dump = new MysqlDump();
183
        $dump->setDefaultDatabase($dbName);
184
        // Disable adding indexes for foreign keys on the current schema, if the new schema doesn't have the foreign
185
        // key then both the foreign key and the index would be dropped however the index won't exist.
186
        $dump->setAddIndexForForeignKey(false);
187
        $dump->parse($stream);
188
189
        return $dump;
190
    }
191
192
    /**
193
     * @param string[] $schemaDefinitionPaths
194
     * @param string $dbName
195
     *
196
     * @return MySqlDump
197
     */
198
    private function getTargetSchema(array $schemaDefinitionPaths, $dbName)
199
    {
200
        return MysqlDump::parseFromPaths(
201
            $schemaDefinitionPaths,
202
            $this->engine,
203
            $this->collation,
204
            $dbName
205
        );
206
    }
207
208
    /**
209
     * @param Connection $connection
210
     * @param string $connectionName
211
     * @param array $diff
212
     * @throws Exception
213
     */
214
    private function applyChanges(Connection $connection, $connectionName, array $diff)
215
    {
216
        if (count($diff) == 0) {
217
            return;
218
        }
219
        if ($this->applyChanges == 'no') {
220
            return;
221
        }
222
    
223
        if ($this->disableForeignKeyChecks) {
224
            $connection->executeQuery('SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0');
225
        }
226
227
        $confirm = $this->applyChanges == 'confirm';
228
        $defaultResponse = 'y';
229
        $logHandle = null;
230
231
        if (!is_null($this->logDir)) {
0 ignored issues
show
The condition is_null($this->logDir) is always false.
Loading history...
232
            $logFile = "{$this->logDir}/{$connectionName}.sql";
233
            $logHandle = fopen($logFile, "w");
234
            if ($logHandle == false) {
235
                fprintf(STDERR, "Could not open log file for writing: $logFile\n");
236
                exit(1);
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
237
            }
238
        }
239
240
        if (count($diff) > 0 && $confirm) {
241
            echo "\n";
242
            echo "-- Confirm changes to $connectionName:\n";
243
        }
244
245
        foreach ($diff as $query) {
246
            $response = $defaultResponse;
247
            $apply = false;
248
249
            if ($confirm) {
250
                echo "\n";
251
                echo "$query;\n\n";
252
                do {
253
                    echo "-- Apply this change? [y]es [n]o [a]ll [q]uit: ";
254
                    $response = fgets(STDIN);
255
                    if ($response === false) {
256
                        throw new Exception("Could not read response");
257
                    }
258
                    $response = rtrim($response);
259
                } while (!in_array($response, ['y', 'n', 'a', 'q']));
260
            }
261
262
            switch ($response) {
263
                case 'y':
264
                    $apply = true;
265
                    break;
266
267
                case 'n':
268
                    $apply = false;
269
                    break;
270
271
                case 'a':
272
                    $apply = true;
273
                    $confirm = false;
274
                    $defaultResponse = 'y';
275
                    break;
276
277
                case 'q':
278
                    $apply = false;
279
                    $confirm = false;
280
                    $defaultResponse = 'n';
281
                    break;
282
            }
283
284
            if ($apply) {
285
                if ($logHandle) {
286
                    fwrite($logHandle, "$query;\n\n");
287
                }
288
                $connection->executeQuery($query);
289
            } elseif ($logHandle && $this->logSkipped) {
290
                fwrite(
291
                    $logHandle,
292
                    "-- [SKIPPED]\n" .
293
                    preg_replace('/^/xms', '-- ', $query) .  ";\n" .
294
                    "\n"
295
                );
296
            }
297
        }
298
299
        if ($this->disableForeignKeyChecks) {
300
            $connection->executeQuery('SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS');
301
        }
302
    }
303
304
    /**
305
     * @param InputInterface $input
306
     * @param OutputInterface $output
307
     * @throws Exception
308
     */
309
    protected function execute(InputInterface $input, OutputInterface $output)
310
    {
311
        $this->configFile = $input->getArgument(self::ARGUMENT_CONFIG_FILE);
312
        $this->connectionNames = $input->getArgument(self::ARGUMENT_CONNECTIONS);
313
314
        if ($input->getOption(self::OPTION_NO_QUOTE_NAMES)) {
315
            $this->quoteNames = false;
316
        }
317
318
        if ($input->getOption(self::OPTION_NO_CREATE_TABLE)) {
319
            $this->createTable = false;
320
        }
321
322
        if ($input->getOption(self::OPTION_NO_DROP_TABLE)) {
323
            $this->dropTable = false;
324
        }
325
326
        $this->applyChanges = $input->getOption(self::OPTION_APPLY_CHANGES);
327
        if (!in_array($this->applyChanges, ['yes', 'no', 'confirm'])) {
328
            throw new InvalidArgumentException(sprintf(
329
                "Unknown value for --%s: %s",
330
                self::OPTION_APPLY_CHANGES,
331
                $this->applyChanges
332
            ));
333
        }
334
335
        if ($input->getOption(self::OPTION_NO_LOG_SKIPPED)) {
336
            $this->logSkipped = false;
337
        }
338
339
        if ($input->getOption(self::OPTION_NO_FOREIGN_KEY_CHECKS)) {
340
            $this->disableForeignKeyChecks = true;
341
        }
342
343
        try {
344
            $config = new Config($this->configFile);
345
            $config->parse();
346
347
            $connectionNames =
348
                (count($this->connectionNames) > 0)
349
                    ? $this->connectionNames
350
                    : $config->getConnectionNames();
351
352
            Token::setQuoteNames($this->quoteNames);
353
354
            if (!is_null($this->logDir)) {
0 ignored issues
show
The condition is_null($this->logDir) is always false.
Loading history...
355
                if (!is_dir($this->logDir)) {
356
                    if (!@mkdir($this->logDir, 0755, true)) {
357
                        fprintf(STDERR, "Could not create log directory: {$this->logDir}\n");
358
                        exit(1);
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
359
                    }
360
                }
361
            }
362
363
            foreach ($connectionNames as $connectionName) {
364
                $output->writeln("-- --------------------------------");
365
                $output->writeln("--   Connection: $connectionName");
366
                $output->writeln("-- --------------------------------");
367
                $connection = $config->getConnection($connectionName);
368
                $entry = $config->getEntry($connectionName);
369
                $dbName = $entry['connection']['dbname'];
370
                $schemaDefinitionPaths = $entry['morphism']['schemaDefinitionPath'];
371
                $matchTables = [
372
                    $dbName => $entry['morphism']['matchTables'],
373
                ];
374
375
                $currentSchema = $this->getCurrentSchema($connection, $dbName);
376
                $targetSchema = $this->getTargetSchema($schemaDefinitionPaths, $dbName);
377
378
                $diff = $currentSchema->diff(
379
                    $targetSchema,
380
                    [
381
                        'createTable'    => $this->createTable,
382
                        'dropTable'      => $this->dropTable,
383
                        'alterEngine'    => $this->alterEngine,
384
                        'matchTables'    => $matchTables,
385
                    ]
386
                );
387
388
                $statements = array_reduce($diff, function ($acc, $item) {
389
                    if (is_array($item)) {
390
                        foreach ($item as $statement) {
391
                            $acc[] = $statement;
392
                        }
393
                    } else {
394
                        $acc[] = $item;
395
                    }
396
397
                    return $acc;
398
                }, []);
399
400
                foreach ($statements as $query) {
401
                    $output->writeln("$query;\n");
402
                }
403
404
                $this->applyChanges($connection, $connectionName, $statements);
405
            }
406
        } catch (RuntimeException $e) {
407
            throw $e;
408
        } catch (Exception $e) {
409
            throw new Exception($e->getMessage() . "\n\n" . $e->getTraceAsString());
410
        }
411
    }
412
}
413