-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathWriteSchemaFile.php
More file actions
146 lines (121 loc) · 5.49 KB
/
Copy pathWriteSchemaFile.php
File metadata and controls
146 lines (121 loc) · 5.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<?php
namespace MatchBot\Application\Commands;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\ExpectationFailedException;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* A file with the full schema kept it git may make it easier for us to see changes etc.
*/
#[AsCommand(
name: 'matchbot:write-schema-files',
description: "Writes a schema file for reference. Inspired by Ruby On Rails's schema.rb file",
)]
class WriteSchemaFile extends Command
{
public function __construct(private EntityManagerInterface $em)
{
parent::__construct(null);
}
#[\Override]
public function configure(): void
{
$this->addOption(
'check',
description: 'Checks if existing schema files match database, instead of writing new files',
);
}
#[\Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$hasError = false;
$connection = $this->em->getConnection();
/** @var list<string> $tables */
$tables = $connection->executeQuery('SHOW TABLES')->fetchFirstColumn();
$schemaDir = dirname(__DIR__, 3) . '/schema';
$fileNames = scandir($schemaDir);
\assert(is_array($fileNames));
$fileNamesOnDisk = array_filter($fileNames, fn(string $fileName) => str_ends_with($fileName, '.sql'));
$checkModeOn = $input->getOption('check');
\assert(is_bool($checkModeOn));
if ($checkModeOn) {
foreach ($fileNamesOnDisk as $fileName) {
$tableName = substr($fileName, 0, -4);
if (!in_array($tableName, $tables, true)) {
$error = sprintf(
"%s/%s is on disc but table %s is not in database, please delete or add to db",
$schemaDir,
$fileName,
$tableName
);
$io->error($error);
$hasError = true;
}
}
}
foreach ($tables as $table) {
if (!in_array("$table.sql", $fileNamesOnDisk, true) && $checkModeOn) {
$error = sprintf("Did not find expected file %s/%s.sql", $schemaDir, $table);
$io->error($error);
$hasError = true;
}
$description = $connection->executeQuery("SHOW CREATE TABLE `$table`")->fetchAllAssociative();
$createTableStatement = $description[0]['Create Table'] ?? $description[0]['Create View'];
\assert(is_string($createTableStatement));
/* AUTO_INCREMENT varies depending on data (not metadata) in db, not suitable to commit) */
$createTableStatement = (string)\preg_replace('/AUTO_INCREMENT=\d+ /', '', $createTableStatement);
/* Charset and collation vary between local docker setup and circleci. Leave out of files for now to allow
comparisons
Local has `ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci``
Circle CI has `ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci`
*/
$createTableStatement = (string)\preg_replace(
"/\) ENGINE=InnoDB DEFAULT CHARSET.*/",
")\n",
$createTableStatement
);
$createTableStatement = (string)\preg_replace('/ COLLATE utf8mb[34]_[a-z_]+/', '', $createTableStatement);
$filename = "{$schemaDir}/{$table}.sql";
$sql = <<<"SQL"
-- Auto generated file, do not edit.
-- run ./matchbot matchbot:write-schema-files to update
-- Note that CHARSET and COLLATE details have been removed to allow sucessful comparisons between schemas
-- on dev machines and CircleCI. Do not use these files to create a database. They are provided for
-- information only.
$createTableStatement
SQL;
if ($checkModeOn) {
try {
Assert::assertSame($sql, file_get_contents($filename));
$io->writeln("$filename matches db schema");
} catch (ExpectationFailedException $exception) { // @phpstan-ignore catch.internalClass
$io->error("$filename content not as expected based on DB schema");
/** @psalm-suppress InternalMethod */
$io->writeln(
$exception->getComparisonFailure()?->getDiff() // @phpstan-ignore method.internalClass
?? throw new \Exception('Missing diff')
);
$hasError = true;
}
} else {
$io->writeln("Writing $filename");
file_put_contents($filename, $sql);
}
}
if ($checkModeOn && ! $hasError) {
$io->success("SQL files in $schemaDir/ match DB schema");
}
if ($hasError) {
$io->error(
"Differences found between files and schema.\n" .
"Run ./matchbot matchbot:write-schema-files to update files from DB schema"
);
}
return $hasError ? 1 : 0;
}
}