-
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathExecuteCommand.php
More file actions
204 lines (179 loc) · 5.88 KB
/
ExecuteCommand.php
File metadata and controls
204 lines (179 loc) · 5.88 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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
<?php
namespace Gt\Database\Cli;
use Gt\Cli\Argument\ArgumentValueList;
use Gt\Cli\Command\Command;
use Gt\Cli\Parameter\NamedParameter;
use Gt\Cli\Parameter\Parameter;
use Gt\Config\ConfigFactory;
use Gt\Database\Connection\Settings;
use Gt\Database\Migration\MigrationIntegrityException;
use Gt\Database\Migration\Migrator;
use Gt\Database\StatementExecutionException;
use Gt\Database\StatementPreparationException;
class ExecuteCommand extends Command {
public function run(?ArgumentValueList $arguments = null):void {
$repoBasePath = getcwd();
$defaultPath = $this->getDefaultPath($repoBasePath);
$config = $this->getConfig($repoBasePath, $defaultPath);
$settings = $this->buildSettingsFromConfig($config, $repoBasePath);
[$migrationPath, $migrationTable] = $this->getMigrationLocation($config, $repoBasePath);
$migrator = new Migrator($settings, $migrationPath, $migrationTable);
$migrator->setOutput(
$this->stream->getOutStream(),
$this->stream->getErrorStream()
);
if($this->isForced($arguments)) {
$migrator->deleteAndRecreateSchema();
}
$migrator->selectSchema();
$migrator->createMigrationTable();
$migrationCount = $migrator->getMigrationCount();
$migrationFileList = $migrator->getMigrationFileList();
$runFrom = $this->calculateResetNumber($arguments, $migrationFileList, $migrator, $migrationCount);
$this->executeMigrations($migrator, $migrationFileList, $runFrom);
}
/** Determine whether the --force flag was provided. */
private function isForced(?ArgumentValueList $arguments):bool {
return $arguments?->contains("force") ?? false;
}
/** Build Settings from config for the current repository. */
private function buildSettingsFromConfig(\Gt\Config\Config $config, string $repoBasePath): Settings {
return new Settings(
implode(DIRECTORY_SEPARATOR, [
$repoBasePath,
$config->get("database.query_path")
]),
$config->get("database.driver") ?? 'mysql',
$config->get("database.schema"),
$config->get("database.host") ?? "localhost",
(int)($config->get("database.port") ?? "3306"),
$config->get("database.username"),
$config->get("database.password")
);
}
/**
* Return [migrationPath, migrationTable] derived from config.
*
* @return list<string>
*/
private function getMigrationLocation(\Gt\Config\Config $config, string $repoBasePath): array {
$migrationPath = implode(DIRECTORY_SEPARATOR, [
$repoBasePath,
$config->get("database.query_path") ?? "query",
$config->get("database.migration_path") ?? "_migration",
]);
$migrationTable = $config->get("database.migration_table") ?? "_migration";
return [$migrationPath, $migrationTable];
}
/**
* Calculate the migration start point from --reset or current migration count.
*
* @param list<string> $migrationFileList
*/
private function calculateResetNumber(
?ArgumentValueList $arguments,
array $migrationFileList,
Migrator $migrator,
int $migrationCount
): int {
$resetNumber = null;
if($arguments?->contains("reset")) {
$resetNumber = $arguments->get("reset")->get();
if(!$resetNumber) {
$lastKey = array_key_last($migrationFileList);
$lastNumber = $migrator->extractNumberFromFilename($migrationFileList[$lastKey]);
$resetNumber = max(0, $lastNumber - 1);
}
$resetNumber = (int)$resetNumber;
}
return $resetNumber ?? $migrationCount;
}
/**
* Wrap integrity check and perform migration with error handling.
*
* @param list<string> $migrationFileList
*/
private function executeMigrations(Migrator $migrator, array $migrationFileList, int $runFrom): void {
try {
$migrator->checkIntegrity($migrationFileList, $runFrom);
$migrator->performMigration($migrationFileList, $runFrom);
}
catch(MigrationIntegrityException $exception) {
$this->writeLine(
"There was an integrity error migrating file '"
. $exception->getMessage()
. "' - this migration is recorded to have been run already, "
. "but the contents of the file has changed.\nFor help, see "
. "https://www.php.gt/database/migrations#integrity-error");
}
catch(StatementPreparationException|StatementExecutionException $exception) {
$this->writeLine(
"There was an error executing migration file: "
. $exception->getMessage()
. "'\nFor help, see https://www.php.gt/database/migrations#error"
);
}
}
public function getName():string {
return "execute";
}
public function getDescription():string {
return "Perform a database migration";
}
public function getRequiredNamedParameterList():array {
return [];
}
public function getOptionalNamedParameterList():array {
// TODO: It would be an improvement to allow passing database settings here rather than always require a config.ini
return [];
}
public function getRequiredParameterList():array {
return [];
}
public function getOptionalParameterList():array {
return [
new Parameter(
false,
"force",
"f",
"Forcefully drop the current schema and run from migration 1"
),
new Parameter(
true,
"reset",
"r",
"Reset the integrity checks to a specific migration number"
)
];
}
private function getDefaultPath(string $repoBasePath):?string {
$defaultPath = implode(DIRECTORY_SEPARATOR, [
$repoBasePath,
"vendor",
"phpgt",
"webengine",
]);
foreach(["config.default.ini", "default.ini"] as $defaultFile) {
$defaultFilePath = $defaultPath . DIRECTORY_SEPARATOR . $defaultFile;
if(is_file($defaultFilePath)) {
return $defaultFilePath;
}
}
return null;
}
/**
* @param bool|string $repoBasePath
* @param string|null $defaultPath
* @return \Gt\Config\Config
*/
protected function getConfig(bool|string $repoBasePath, ?string $defaultPath):\Gt\Config\Config {
$config = ConfigFactory::createForProject($repoBasePath);
$default = $defaultPath
? ConfigFactory::createFromPathName($defaultPath)
: null;
if($default) {
$config = $config->withMerge($default);
}
return $config;
}
}