Skip to content

Commit 192e89e

Browse files
authored
Merge pull request #4 from orisintel/feature/config
Feature/config
2 parents 3cc6972 + 029c282 commit 192e89e

File tree

7 files changed

+123
-23
lines changed

7 files changed

+123
-23
lines changed

config/migration-snapshot.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
return [
4+
/*
5+
|--------------------------------------------------------------------------
6+
| Which environments to implicitly dump/load
7+
|--------------------------------------------------------------------------
8+
|
9+
| Comma separated list of environments which are safe to implicitly dump or
10+
| load when executing `php artisan migrate`.
11+
|
12+
*/
13+
14+
'environments' => env('MIGRATION_SNAPSHOT_ENVIRONMENTS', 'development,local,testing'),
15+
16+
/*
17+
|--------------------------------------------------------------------------
18+
| Whether to reorder the `migrations` rows for consistency.
19+
|--------------------------------------------------------------------------
20+
|
21+
| The order migrations are applied in development may vary from person to
22+
| person, especially as they are created in parallel. This option reorders
23+
| the migration records for consistency so the output file can be managed
24+
| in source control.
25+
|
26+
| If the order migrations are applied will produce significant differences,
27+
| such as changing the behavior of the app, then this should be left
28+
| disabled. In such cases `migrate:fresh --database=test` followed by
29+
| `migrate` or `migrate:dump` can achieve similar consistency.
30+
|
31+
*/
32+
33+
'reorder' => env('MIGRATION_SNAPSHOT_REORDER', false),
34+
];

src/Commands/MigrateDumpCommand.php

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,35 @@ public function handle()
5656
$this->info('Dumped schema');
5757
}
5858

59+
private static function reorderMigrationRows(array $output) : array
60+
{
61+
if (config('migration-snapshot.reorder')) {
62+
$reordered = [];
63+
$new_id = 1;
64+
foreach ($output as $line) {
65+
// Extract parts of "INSERT ... VALUES ([id],'[ver]',[batch])
66+
// where version begins with "YYYY_MM_DD_HHMMSS".
67+
$occurrences = preg_match(
68+
"/^(.*?VALUES\s*)\([0-9]+,\s*'([0-9_]{17})(.*?),\s*[0-9]+\s*\)\s*;\s*$/iu",
69+
$line,
70+
$m
71+
);
72+
if (1 !== $occurrences) {
73+
throw new \UnexpectedValueException(
74+
'Only insert rows supported:' . PHP_EOL . var_export($line, 1)
75+
);
76+
}
77+
// Reassemble parts with new values and index by timestamp of
78+
// version string to sort.
79+
$reordered[$m[2]] = "$m[1]($new_id,'$m[2]$m[3],0);";
80+
$new_id += 1;
81+
}
82+
return $reordered;
83+
}
84+
85+
return $output;
86+
}
87+
5988
/**
6089
* @param array $db_config like ['host' => , 'port' => ].
6190
* @param string $schema_sql_path like '.../schema.sql'
@@ -101,13 +130,22 @@ private static function mysqlDump(array $db_config, string $schema_sql_path) : i
101130
}
102131

103132
// Include migration rows to avoid unnecessary reruns conflicting.
104-
// CONSIDER: How this could be done as consistent snapshot with
105-
// dump of structure, and avoid duplicate "SET" comments.
106-
passthru(
107-
$command_prefix . ' migrations --no-create-info --skip-extended-insert >> '
108-
. escapeshellarg($schema_sql_path),
133+
exec(
134+
$command_prefix . ' migrations --no-create-info --skip-extended-insert --compact',
135+
$output,
109136
$exit_code
110137
);
138+
if (0 !== $exit_code) {
139+
return $exit_code;
140+
}
141+
142+
$output = self::reorderMigrationRows($output);
143+
144+
file_put_contents(
145+
$schema_sql_path,
146+
implode(PHP_EOL, $output),
147+
FILE_APPEND
148+
);
111149

112150
return $exit_code;
113151
}
@@ -130,7 +168,6 @@ private static function pgsqlDump(array $db_config, string $schema_sql_path) : i
130168
. ' --port=' . escapeshellarg($db_config['port'])
131169
. ' --username=' . escapeshellarg($db_config['username'])
132170
. ' ' . escapeshellarg($db_config['database']);
133-
// TODO: Suppress warning about insecure password.
134171
passthru(
135172
$command_prefix
136173
. ' --file=' . escapeshellarg($schema_sql_path)
@@ -142,13 +179,22 @@ private static function pgsqlDump(array $db_config, string $schema_sql_path) : i
142179
}
143180

144181
// Include migration rows to avoid unnecessary reruns conflicting.
145-
// CONSIDER: How this could be done as consistent snapshot with
146-
// dump of structure, and avoid duplicate "SET" comments.
147-
passthru(
148-
$command_prefix . ' --table=migrations --data-only --inserts >> '
149-
. escapeshellarg($schema_sql_path),
182+
exec(
183+
$command_prefix . ' --table=migrations --data-only --inserts',
184+
$output,
150185
$exit_code
151186
);
187+
if (0 !== $exit_code) {
188+
return $exit_code;
189+
}
190+
191+
$output = self::reorderMigrationRows($output);
192+
193+
file_put_contents(
194+
$schema_sql_path,
195+
implode(PHP_EOL, $output),
196+
FILE_APPEND
197+
);
152198

153199
return $exit_code;
154200
}
@@ -167,24 +213,40 @@ private static function sqliteDump(array $db_config, string $schema_sql_path) :
167213
// Since Sqlite lacks Information Schema, and dumping everything may be
168214
// too slow or memory intense, just query tables and dump them
169215
// individually.
216+
// CONSIDER: Using Laravel's `Schema` code instead.
170217
exec($command_prefix . ' .tables', $output, $exit_code);
171218
if (0 !== $exit_code) {
172219
return $exit_code;
173220
}
174221
$tables = preg_split('/\s+/', implode(' ', $output));
175222

223+
file_put_contents($schema_sql_path, '');
224+
176225
foreach ($tables as $table) {
177226
// Only migrations should dump data with schema.
178227
$sql_command = 'migrations' === $table ? '.dump' : '.schema';
179228

180-
passthru(
181-
$command_prefix . ' ' . escapeshellarg("$sql_command $table")
182-
. ' >> ' . escapeshellarg($schema_sql_path),
229+
$output = [];
230+
exec(
231+
$command_prefix . ' ' . escapeshellarg("$sql_command $table"),
232+
$output,
183233
$exit_code
184234
);
185235
if (0 !== $exit_code) {
186236
return $exit_code;
187237
}
238+
239+
if ('migrations' === $table) {
240+
$insert_rows = array_slice($output, 4, -1);
241+
$sorted = self::reorderMigrationRows($insert_rows);
242+
array_splice($output, 4, -1, $sorted);
243+
}
244+
245+
file_put_contents(
246+
$schema_sql_path,
247+
implode(PHP_EOL, $output) . PHP_EOL,
248+
FILE_APPEND
249+
);
188250
}
189251

190252
return $exit_code;

src/Handlers/MigrateFinishedHandler.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ public function handle(CommandFinished $event)
1414
'migrate' === $event->command // CONSIDER: Also `migrate:fresh`.
1515
&& ! $event->input->hasParameterOption(['--help', '--pretend', '-V', '--version'])
1616
&& env('MIGRATION_SNAPSHOT', true)
17-
// CONSIDER: Making configurable blacklist of environments.
18-
&& 'production' !== app()->environment()
17+
&& in_array(app()->environment(), explode(',', config('migration-snapshot.environments')), true)
1918
) {
2019
$options = MigrateStartingHandler::inputToArtisanOptions($event->input);
2120
$database = $options['--database'] ?? env('DB_CONNECTION');
@@ -28,4 +27,4 @@ public function handle(CommandFinished $event)
2827
\Artisan::call('migrate:dump', $options, $event->output);
2928
}
3029
}
31-
}
30+
}

src/Handlers/MigrateStartingHandler.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ public function handle(CommandStarting $event)
5858
&& env('MIGRATION_SNAPSHOT', true) // CONSIDER: Config option.
5959
// Never implicitly load fresh (from file) in production since it
6060
// would need to drop first, and that would be destructive.
61-
// CONSIDER: Making configurable blacklist of environments.
62-
&& 'production' !== app()->environment()
61+
&& in_array(app()->environment(), explode(',', config('migration-snapshot.environments')), true)
6362
// No point in implicitly loading when it's not present.
6463
&& file_exists(database_path() . MigrateDumpCommand::SCHEMA_SQL_PATH_SUFFIX)
6564
) {
@@ -149,4 +148,4 @@ private static function inputValidateWorkaround($input) : bool
149148

150149
return true;
151150
}
152-
}
151+
}

src/ServiceProvider.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ class ServiceProvider extends \Illuminate\Support\ServiceProvider
1010
public function boot()
1111
{
1212
if ($this->app->runningInConsole()) {
13+
$this->publishes([
14+
__DIR__ . '/../config/migration-snapshot.php' => config_path('migration-snapshot.php'),
15+
], 'config');
16+
1317
$this->commands([
1418
MigrateDumpCommand::class,
1519
MigrateLoadCommand::class,
@@ -19,6 +23,8 @@ public function boot()
1923

2024
public function register()
2125
{
26+
$this->mergeConfigFrom(__DIR__ . '/../config/migration-snapshot.php', 'migration-snapshot');
27+
2228
$this->app->register(EventServiceProvider::class);
2329
}
24-
}
30+
}

tests/MigrateHookTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ public function test_handle_doesNotLoadWhenDbHasMigrated()
4646

4747
$this->assertEquals(1, \DB::table('test_ms')->count());
4848
}
49-
}
49+
}

tests/Sqlite/MigrateLoadTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@ public function test_handle()
2323

2424
$this->assertNull(\DB::table('test_ms')->value('name'));
2525
}
26-
}
26+
}

0 commit comments

Comments
 (0)