Completed
Pull Request — master (#9167)
by Robin
31:55 queued 14:45
created
lib/public/Migration/IMigrationStep.php 1 patch
Indentation   +36 added lines, -36 removed lines patch added patch discarded remove patch
@@ -29,44 +29,44 @@
 block discarded – undo
29 29
  * @since 13.0.0
30 30
  */
31 31
 interface IMigrationStep {
32
-	/**
33
-	 * Human readable name of the migration step
34
-	 *
35
-	 * @return string
36
-	 * @since 14.0.0
37
-	 */
38
-	public function name(): string;
32
+    /**
33
+     * Human readable name of the migration step
34
+     *
35
+     * @return string
36
+     * @since 14.0.0
37
+     */
38
+    public function name(): string;
39 39
 
40
-	/**
41
-	 * Human readable description of the migration steps
42
-	 *
43
-	 * @return string
44
-	 * @since 14.0.0
45
-	 */
46
-	public function description(): string;
40
+    /**
41
+     * Human readable description of the migration steps
42
+     *
43
+     * @return string
44
+     * @since 14.0.0
45
+     */
46
+    public function description(): string;
47 47
 
48
-	/**
49
-	 * @param IOutput $output
50
-	 * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
51
-	 * @param array $options
52
-	 * @since 13.0.0
53
-	 */
54
-	public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options);
48
+    /**
49
+     * @param IOutput $output
50
+     * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
51
+     * @param array $options
52
+     * @since 13.0.0
53
+     */
54
+    public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options);
55 55
 
56
-	/**
57
-	 * @param IOutput $output
58
-	 * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
59
-	 * @param array $options
60
-	 * @return null|ISchemaWrapper
61
-	 * @since 13.0.0
62
-	 */
63
-	public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options);
56
+    /**
57
+     * @param IOutput $output
58
+     * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
59
+     * @param array $options
60
+     * @return null|ISchemaWrapper
61
+     * @since 13.0.0
62
+     */
63
+    public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options);
64 64
 
65
-	/**
66
-	 * @param IOutput $output
67
-	 * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
68
-	 * @param array $options
69
-	 * @since 13.0.0
70
-	 */
71
-	public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options);
65
+    /**
66
+     * @param IOutput $output
67
+     * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
68
+     * @param array $options
69
+     * @since 13.0.0
70
+     */
71
+    public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options);
72 72
 }
Please login to merge, or discard this patch.
lib/private/DB/MigrationService.php 1 patch
Indentation   +427 added lines, -427 removed lines patch added patch discarded remove patch
@@ -35,431 +35,431 @@
 block discarded – undo
35 35
 
36 36
 class MigrationService {
37 37
 
38
-	/** @var boolean */
39
-	private $migrationTableCreated;
40
-	/** @var array */
41
-	private $migrations;
42
-	/** @var IOutput */
43
-	private $output;
44
-	/** @var Connection */
45
-	private $connection;
46
-	/** @var string */
47
-	private $appName;
48
-
49
-	/**
50
-	 * MigrationService constructor.
51
-	 *
52
-	 * @param $appName
53
-	 * @param IDBConnection $connection
54
-	 * @param AppLocator $appLocator
55
-	 * @param IOutput|null $output
56
-	 * @throws \Exception
57
-	 */
58
-	public function __construct($appName, IDBConnection $connection, IOutput $output = null, AppLocator $appLocator = null) {
59
-		$this->appName = $appName;
60
-		$this->connection = $connection;
61
-		$this->output = $output;
62
-		if (null === $this->output) {
63
-			$this->output = new SimpleOutput(\OC::$server->getLogger(), $appName);
64
-		}
65
-
66
-		if ($appName === 'core') {
67
-			$this->migrationsPath = \OC::$SERVERROOT . '/core/Migrations';
68
-			$this->migrationsNamespace = 'OC\\Core\\Migrations';
69
-		} else {
70
-			if (null === $appLocator) {
71
-				$appLocator = new AppLocator();
72
-			}
73
-			$appPath = $appLocator->getAppPath($appName);
74
-			$namespace = App::buildAppNamespace($appName);
75
-			$this->migrationsPath = "$appPath/lib/Migration";
76
-			$this->migrationsNamespace = $namespace . '\\Migration';
77
-		}
78
-	}
79
-
80
-	/**
81
-	 * Returns the name of the app for which this migration is executed
82
-	 *
83
-	 * @return string
84
-	 */
85
-	public function getApp() {
86
-		return $this->appName;
87
-	}
88
-
89
-	/**
90
-	 * @return bool
91
-	 * @codeCoverageIgnore - this will implicitly tested on installation
92
-	 */
93
-	private function createMigrationTable() {
94
-		if ($this->migrationTableCreated) {
95
-			return false;
96
-		}
97
-
98
-		$schema = new SchemaWrapper($this->connection);
99
-
100
-		/**
101
-		 * We drop the table when it has different columns or the definition does not
102
-		 * match. E.g. ownCloud uses a length of 177 for app and 14 for version.
103
-		 */
104
-		try {
105
-			$table = $schema->getTable('migrations');
106
-			$columns = $table->getColumns();
107
-
108
-			if (count($columns) === 2) {
109
-				try {
110
-					$column = $table->getColumn('app');
111
-					$schemaMismatch = $column->getLength() !== 255;
112
-
113
-					if (!$schemaMismatch) {
114
-						$column = $table->getColumn('version');
115
-						$schemaMismatch = $column->getLength() !== 255;
116
-					}
117
-				} catch (SchemaException $e) {
118
-					// One of the columns is missing
119
-					$schemaMismatch = true;
120
-				}
121
-
122
-				if (!$schemaMismatch) {
123
-					// Table exists and schema matches: return back!
124
-					$this->migrationTableCreated = true;
125
-					return false;
126
-				}
127
-			}
128
-
129
-			// Drop the table, when it didn't match our expectations.
130
-			$this->connection->dropTable('migrations');
131
-
132
-			// Recreate the schema after the table was dropped.
133
-			$schema = new SchemaWrapper($this->connection);
134
-
135
-		} catch (SchemaException $e) {
136
-			// Table not found, no need to panic, we will create it.
137
-		}
138
-
139
-		$table = $schema->createTable('migrations');
140
-		$table->addColumn('app', Type::STRING, ['length' => 255]);
141
-		$table->addColumn('version', Type::STRING, ['length' => 255]);
142
-		$table->setPrimaryKey(['app', 'version']);
143
-
144
-		$this->connection->migrateToSchema($schema->getWrappedSchema());
145
-
146
-		$this->migrationTableCreated = true;
147
-
148
-		return true;
149
-	}
150
-
151
-	/**
152
-	 * Returns all versions which have already been applied
153
-	 *
154
-	 * @return string[]
155
-	 * @codeCoverageIgnore - no need to test this
156
-	 */
157
-	public function getMigratedVersions() {
158
-		$this->createMigrationTable();
159
-		$qb = $this->connection->getQueryBuilder();
160
-
161
-		$qb->select('version')
162
-			->from('migrations')
163
-			->where($qb->expr()->eq('app', $qb->createNamedParameter($this->getApp())))
164
-			->orderBy('version');
165
-
166
-		$result = $qb->execute();
167
-		$rows = $result->fetchAll(\PDO::FETCH_COLUMN);
168
-		$result->closeCursor();
169
-
170
-		return $rows;
171
-	}
172
-
173
-	/**
174
-	 * Returns all versions which are available in the migration folder
175
-	 *
176
-	 * @return array
177
-	 */
178
-	public function getAvailableVersions() {
179
-		$this->ensureMigrationsAreLoaded();
180
-		return array_map('strval', array_keys($this->migrations));
181
-	}
182
-
183
-	protected function findMigrations() {
184
-		$directory = realpath($this->migrationsPath);
185
-		if ($directory === false || !file_exists($directory) || !is_dir($directory)) {
186
-			return [];
187
-		}
188
-
189
-		$iterator = new \RegexIterator(
190
-			new \RecursiveIteratorIterator(
191
-				new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS),
192
-				\RecursiveIteratorIterator::LEAVES_ONLY
193
-			),
194
-			'#^.+\\/Version[^\\/]{1,255}\\.php$#i',
195
-			\RegexIterator::GET_MATCH);
196
-
197
-		$files = array_keys(iterator_to_array($iterator));
198
-		uasort($files, function ($a, $b) {
199
-			preg_match('/^Version(\d+)Date(\d+)\\.php$/', basename($a), $matchA);
200
-			preg_match('/^Version(\d+)Date(\d+)\\.php$/', basename($b), $matchB);
201
-			if (!empty($matchA) && !empty($matchB)) {
202
-				if ($matchA[1] !== $matchB[1]) {
203
-					return ($matchA[1] < $matchB[1]) ? -1 : 1;
204
-				}
205
-				return ($matchA[2] < $matchB[2]) ? -1 : 1;
206
-			}
207
-			return (basename($a) < basename($b)) ? -1 : 1;
208
-		});
209
-
210
-		$migrations = [];
211
-
212
-		foreach ($files as $file) {
213
-			$className = basename($file, '.php');
214
-			$version = (string) substr($className, 7);
215
-			if ($version === '0') {
216
-				throw new \InvalidArgumentException(
217
-					"Cannot load a migrations with the name '$version' because it is a reserved number"
218
-				);
219
-			}
220
-			$migrations[$version] = sprintf('%s\\%s', $this->migrationsNamespace, $className);
221
-		}
222
-
223
-		return $migrations;
224
-	}
225
-
226
-	/**
227
-	 * @param string $to
228
-	 * @return string[]
229
-	 */
230
-	private function getMigrationsToExecute($to) {
231
-		$knownMigrations = $this->getMigratedVersions();
232
-		$availableMigrations = $this->getAvailableVersions();
233
-
234
-		$toBeExecuted = [];
235
-		foreach ($availableMigrations as $v) {
236
-			if ($to !== 'latest' && $v > $to) {
237
-				continue;
238
-			}
239
-			if ($this->shallBeExecuted($v, $knownMigrations)) {
240
-				$toBeExecuted[] = $v;
241
-			}
242
-		}
243
-
244
-		return $toBeExecuted;
245
-	}
246
-
247
-	/**
248
-	 * @param string $m
249
-	 * @param string[] $knownMigrations
250
-	 * @return bool
251
-	 */
252
-	private function shallBeExecuted($m, $knownMigrations) {
253
-		if (in_array($m, $knownMigrations)) {
254
-			return false;
255
-		}
256
-
257
-		return true;
258
-	}
259
-
260
-	/**
261
-	 * @param string $version
262
-	 */
263
-	private function markAsExecuted($version) {
264
-		$this->connection->insertIfNotExist('*PREFIX*migrations', [
265
-			'app' => $this->appName,
266
-			'version' => $version
267
-		]);
268
-	}
269
-
270
-	/**
271
-	 * Returns the name of the table which holds the already applied versions
272
-	 *
273
-	 * @return string
274
-	 */
275
-	public function getMigrationsTableName() {
276
-		return $this->connection->getPrefix() . 'migrations';
277
-	}
278
-
279
-	/**
280
-	 * Returns the namespace of the version classes
281
-	 *
282
-	 * @return string
283
-	 */
284
-	public function getMigrationsNamespace() {
285
-		return $this->migrationsNamespace;
286
-	}
287
-
288
-	/**
289
-	 * Returns the directory which holds the versions
290
-	 *
291
-	 * @return string
292
-	 */
293
-	public function getMigrationsDirectory() {
294
-		return $this->migrationsPath;
295
-	}
296
-
297
-	/**
298
-	 * Return the explicit version for the aliases; current, next, prev, latest
299
-	 *
300
-	 * @param string $alias
301
-	 * @return mixed|null|string
302
-	 */
303
-	public function getMigration($alias) {
304
-		switch($alias) {
305
-			case 'current':
306
-				return $this->getCurrentVersion();
307
-			case 'next':
308
-				return $this->getRelativeVersion($this->getCurrentVersion(), 1);
309
-			case 'prev':
310
-				return $this->getRelativeVersion($this->getCurrentVersion(), -1);
311
-			case 'latest':
312
-				$this->ensureMigrationsAreLoaded();
313
-
314
-				$migrations = $this->getAvailableVersions();
315
-				return @end($migrations);
316
-		}
317
-		return '0';
318
-	}
319
-
320
-	/**
321
-	 * @param string $version
322
-	 * @param int $delta
323
-	 * @return null|string
324
-	 */
325
-	private function getRelativeVersion($version, $delta) {
326
-		$this->ensureMigrationsAreLoaded();
327
-
328
-		$versions = $this->getAvailableVersions();
329
-		array_unshift($versions, 0);
330
-		$offset = array_search($version, $versions, true);
331
-		if ($offset === false || !isset($versions[$offset + $delta])) {
332
-			// Unknown version or delta out of bounds.
333
-			return null;
334
-		}
335
-
336
-		return (string) $versions[$offset + $delta];
337
-	}
338
-
339
-	/**
340
-	 * @return string
341
-	 */
342
-	private function getCurrentVersion() {
343
-		$m = $this->getMigratedVersions();
344
-		if (count($m) === 0) {
345
-			return '0';
346
-		}
347
-		$migrations = array_values($m);
348
-		return @end($migrations);
349
-	}
350
-
351
-	/**
352
-	 * @param string $version
353
-	 * @return string
354
-	 * @throws \InvalidArgumentException
355
-	 */
356
-	private function getClass($version) {
357
-		$this->ensureMigrationsAreLoaded();
358
-
359
-		if (isset($this->migrations[$version])) {
360
-			return $this->migrations[$version];
361
-		}
362
-
363
-		throw new \InvalidArgumentException("Version $version is unknown.");
364
-	}
365
-
366
-	/**
367
-	 * Allows to set an IOutput implementation which is used for logging progress and messages
368
-	 *
369
-	 * @param IOutput $output
370
-	 */
371
-	public function setOutput(IOutput $output) {
372
-		$this->output = $output;
373
-	}
374
-
375
-	/**
376
-	 * Applies all not yet applied versions up to $to
377
-	 *
378
-	 * @param string $to
379
-	 * @throws \InvalidArgumentException
380
-	 */
381
-	public function migrate($to = 'latest') {
382
-		// read known migrations
383
-		$toBeExecuted = $this->getMigrationsToExecute($to);
384
-		foreach ($toBeExecuted as $version) {
385
-			$this->executeStep($version);
386
-		}
387
-	}
388
-
389
-	/**
390
-	 * Get the human readable descriptions for the migration steps to run
391
-	 *
392
-	 * @param string $to
393
-	 * @return string[] [$name => $description]
394
-	 */
395
-	public function describeMigrationStep($to = 'latest') {
396
-		$toBeExecuted = $this->getMigrationsToExecute($to);
397
-		$description = [];
398
-		foreach ($toBeExecuted as $version) {
399
-			$migration = $this->createInstance($version);
400
-			if ($migration->name()) {
401
-				$description[$migration->name()] = $migration->description();
402
-			}
403
-		}
404
-		return $description;
405
-	}
406
-
407
-	/**
408
-	 * @param string $version
409
-	 * @return IMigrationStep
410
-	 * @throws \InvalidArgumentException
411
-	 */
412
-	protected function createInstance($version) {
413
-		$class = $this->getClass($version);
414
-		try {
415
-			$s = \OC::$server->query($class);
416
-
417
-			if (!$s instanceof IMigrationStep) {
418
-				throw new \InvalidArgumentException('Not a valid migration');
419
-			}
420
-		} catch (QueryException $e) {
421
-			if (class_exists($class)) {
422
-				$s = new $class();
423
-			} else {
424
-				throw new \InvalidArgumentException("Migration step '$class' is unknown");
425
-			}
426
-		}
427
-
428
-		return $s;
429
-	}
430
-
431
-	/**
432
-	 * Executes one explicit version
433
-	 *
434
-	 * @param string $version
435
-	 * @throws \InvalidArgumentException
436
-	 */
437
-	public function executeStep($version) {
438
-		$instance = $this->createInstance($version);
439
-
440
-		$instance->preSchemaChange($this->output, function() {
441
-			return new SchemaWrapper($this->connection);
442
-		}, ['tablePrefix' => $this->connection->getPrefix()]);
443
-
444
-		$toSchema = $instance->changeSchema($this->output, function() {
445
-			return new SchemaWrapper($this->connection);
446
-		}, ['tablePrefix' => $this->connection->getPrefix()]);
447
-
448
-		if ($toSchema instanceof SchemaWrapper) {
449
-			$this->connection->migrateToSchema($toSchema->getWrappedSchema());
450
-			$toSchema->performDropTableCalls();
451
-		}
452
-
453
-		$instance->postSchemaChange($this->output, function() {
454
-			return new SchemaWrapper($this->connection);
455
-		}, ['tablePrefix' => $this->connection->getPrefix()]);
456
-
457
-		$this->markAsExecuted($version);
458
-	}
459
-
460
-	private function ensureMigrationsAreLoaded() {
461
-		if (empty($this->migrations)) {
462
-			$this->migrations = $this->findMigrations();
463
-		}
464
-	}
38
+    /** @var boolean */
39
+    private $migrationTableCreated;
40
+    /** @var array */
41
+    private $migrations;
42
+    /** @var IOutput */
43
+    private $output;
44
+    /** @var Connection */
45
+    private $connection;
46
+    /** @var string */
47
+    private $appName;
48
+
49
+    /**
50
+     * MigrationService constructor.
51
+     *
52
+     * @param $appName
53
+     * @param IDBConnection $connection
54
+     * @param AppLocator $appLocator
55
+     * @param IOutput|null $output
56
+     * @throws \Exception
57
+     */
58
+    public function __construct($appName, IDBConnection $connection, IOutput $output = null, AppLocator $appLocator = null) {
59
+        $this->appName = $appName;
60
+        $this->connection = $connection;
61
+        $this->output = $output;
62
+        if (null === $this->output) {
63
+            $this->output = new SimpleOutput(\OC::$server->getLogger(), $appName);
64
+        }
65
+
66
+        if ($appName === 'core') {
67
+            $this->migrationsPath = \OC::$SERVERROOT . '/core/Migrations';
68
+            $this->migrationsNamespace = 'OC\\Core\\Migrations';
69
+        } else {
70
+            if (null === $appLocator) {
71
+                $appLocator = new AppLocator();
72
+            }
73
+            $appPath = $appLocator->getAppPath($appName);
74
+            $namespace = App::buildAppNamespace($appName);
75
+            $this->migrationsPath = "$appPath/lib/Migration";
76
+            $this->migrationsNamespace = $namespace . '\\Migration';
77
+        }
78
+    }
79
+
80
+    /**
81
+     * Returns the name of the app for which this migration is executed
82
+     *
83
+     * @return string
84
+     */
85
+    public function getApp() {
86
+        return $this->appName;
87
+    }
88
+
89
+    /**
90
+     * @return bool
91
+     * @codeCoverageIgnore - this will implicitly tested on installation
92
+     */
93
+    private function createMigrationTable() {
94
+        if ($this->migrationTableCreated) {
95
+            return false;
96
+        }
97
+
98
+        $schema = new SchemaWrapper($this->connection);
99
+
100
+        /**
101
+         * We drop the table when it has different columns or the definition does not
102
+         * match. E.g. ownCloud uses a length of 177 for app and 14 for version.
103
+         */
104
+        try {
105
+            $table = $schema->getTable('migrations');
106
+            $columns = $table->getColumns();
107
+
108
+            if (count($columns) === 2) {
109
+                try {
110
+                    $column = $table->getColumn('app');
111
+                    $schemaMismatch = $column->getLength() !== 255;
112
+
113
+                    if (!$schemaMismatch) {
114
+                        $column = $table->getColumn('version');
115
+                        $schemaMismatch = $column->getLength() !== 255;
116
+                    }
117
+                } catch (SchemaException $e) {
118
+                    // One of the columns is missing
119
+                    $schemaMismatch = true;
120
+                }
121
+
122
+                if (!$schemaMismatch) {
123
+                    // Table exists and schema matches: return back!
124
+                    $this->migrationTableCreated = true;
125
+                    return false;
126
+                }
127
+            }
128
+
129
+            // Drop the table, when it didn't match our expectations.
130
+            $this->connection->dropTable('migrations');
131
+
132
+            // Recreate the schema after the table was dropped.
133
+            $schema = new SchemaWrapper($this->connection);
134
+
135
+        } catch (SchemaException $e) {
136
+            // Table not found, no need to panic, we will create it.
137
+        }
138
+
139
+        $table = $schema->createTable('migrations');
140
+        $table->addColumn('app', Type::STRING, ['length' => 255]);
141
+        $table->addColumn('version', Type::STRING, ['length' => 255]);
142
+        $table->setPrimaryKey(['app', 'version']);
143
+
144
+        $this->connection->migrateToSchema($schema->getWrappedSchema());
145
+
146
+        $this->migrationTableCreated = true;
147
+
148
+        return true;
149
+    }
150
+
151
+    /**
152
+     * Returns all versions which have already been applied
153
+     *
154
+     * @return string[]
155
+     * @codeCoverageIgnore - no need to test this
156
+     */
157
+    public function getMigratedVersions() {
158
+        $this->createMigrationTable();
159
+        $qb = $this->connection->getQueryBuilder();
160
+
161
+        $qb->select('version')
162
+            ->from('migrations')
163
+            ->where($qb->expr()->eq('app', $qb->createNamedParameter($this->getApp())))
164
+            ->orderBy('version');
165
+
166
+        $result = $qb->execute();
167
+        $rows = $result->fetchAll(\PDO::FETCH_COLUMN);
168
+        $result->closeCursor();
169
+
170
+        return $rows;
171
+    }
172
+
173
+    /**
174
+     * Returns all versions which are available in the migration folder
175
+     *
176
+     * @return array
177
+     */
178
+    public function getAvailableVersions() {
179
+        $this->ensureMigrationsAreLoaded();
180
+        return array_map('strval', array_keys($this->migrations));
181
+    }
182
+
183
+    protected function findMigrations() {
184
+        $directory = realpath($this->migrationsPath);
185
+        if ($directory === false || !file_exists($directory) || !is_dir($directory)) {
186
+            return [];
187
+        }
188
+
189
+        $iterator = new \RegexIterator(
190
+            new \RecursiveIteratorIterator(
191
+                new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS),
192
+                \RecursiveIteratorIterator::LEAVES_ONLY
193
+            ),
194
+            '#^.+\\/Version[^\\/]{1,255}\\.php$#i',
195
+            \RegexIterator::GET_MATCH);
196
+
197
+        $files = array_keys(iterator_to_array($iterator));
198
+        uasort($files, function ($a, $b) {
199
+            preg_match('/^Version(\d+)Date(\d+)\\.php$/', basename($a), $matchA);
200
+            preg_match('/^Version(\d+)Date(\d+)\\.php$/', basename($b), $matchB);
201
+            if (!empty($matchA) && !empty($matchB)) {
202
+                if ($matchA[1] !== $matchB[1]) {
203
+                    return ($matchA[1] < $matchB[1]) ? -1 : 1;
204
+                }
205
+                return ($matchA[2] < $matchB[2]) ? -1 : 1;
206
+            }
207
+            return (basename($a) < basename($b)) ? -1 : 1;
208
+        });
209
+
210
+        $migrations = [];
211
+
212
+        foreach ($files as $file) {
213
+            $className = basename($file, '.php');
214
+            $version = (string) substr($className, 7);
215
+            if ($version === '0') {
216
+                throw new \InvalidArgumentException(
217
+                    "Cannot load a migrations with the name '$version' because it is a reserved number"
218
+                );
219
+            }
220
+            $migrations[$version] = sprintf('%s\\%s', $this->migrationsNamespace, $className);
221
+        }
222
+
223
+        return $migrations;
224
+    }
225
+
226
+    /**
227
+     * @param string $to
228
+     * @return string[]
229
+     */
230
+    private function getMigrationsToExecute($to) {
231
+        $knownMigrations = $this->getMigratedVersions();
232
+        $availableMigrations = $this->getAvailableVersions();
233
+
234
+        $toBeExecuted = [];
235
+        foreach ($availableMigrations as $v) {
236
+            if ($to !== 'latest' && $v > $to) {
237
+                continue;
238
+            }
239
+            if ($this->shallBeExecuted($v, $knownMigrations)) {
240
+                $toBeExecuted[] = $v;
241
+            }
242
+        }
243
+
244
+        return $toBeExecuted;
245
+    }
246
+
247
+    /**
248
+     * @param string $m
249
+     * @param string[] $knownMigrations
250
+     * @return bool
251
+     */
252
+    private function shallBeExecuted($m, $knownMigrations) {
253
+        if (in_array($m, $knownMigrations)) {
254
+            return false;
255
+        }
256
+
257
+        return true;
258
+    }
259
+
260
+    /**
261
+     * @param string $version
262
+     */
263
+    private function markAsExecuted($version) {
264
+        $this->connection->insertIfNotExist('*PREFIX*migrations', [
265
+            'app' => $this->appName,
266
+            'version' => $version
267
+        ]);
268
+    }
269
+
270
+    /**
271
+     * Returns the name of the table which holds the already applied versions
272
+     *
273
+     * @return string
274
+     */
275
+    public function getMigrationsTableName() {
276
+        return $this->connection->getPrefix() . 'migrations';
277
+    }
278
+
279
+    /**
280
+     * Returns the namespace of the version classes
281
+     *
282
+     * @return string
283
+     */
284
+    public function getMigrationsNamespace() {
285
+        return $this->migrationsNamespace;
286
+    }
287
+
288
+    /**
289
+     * Returns the directory which holds the versions
290
+     *
291
+     * @return string
292
+     */
293
+    public function getMigrationsDirectory() {
294
+        return $this->migrationsPath;
295
+    }
296
+
297
+    /**
298
+     * Return the explicit version for the aliases; current, next, prev, latest
299
+     *
300
+     * @param string $alias
301
+     * @return mixed|null|string
302
+     */
303
+    public function getMigration($alias) {
304
+        switch($alias) {
305
+            case 'current':
306
+                return $this->getCurrentVersion();
307
+            case 'next':
308
+                return $this->getRelativeVersion($this->getCurrentVersion(), 1);
309
+            case 'prev':
310
+                return $this->getRelativeVersion($this->getCurrentVersion(), -1);
311
+            case 'latest':
312
+                $this->ensureMigrationsAreLoaded();
313
+
314
+                $migrations = $this->getAvailableVersions();
315
+                return @end($migrations);
316
+        }
317
+        return '0';
318
+    }
319
+
320
+    /**
321
+     * @param string $version
322
+     * @param int $delta
323
+     * @return null|string
324
+     */
325
+    private function getRelativeVersion($version, $delta) {
326
+        $this->ensureMigrationsAreLoaded();
327
+
328
+        $versions = $this->getAvailableVersions();
329
+        array_unshift($versions, 0);
330
+        $offset = array_search($version, $versions, true);
331
+        if ($offset === false || !isset($versions[$offset + $delta])) {
332
+            // Unknown version or delta out of bounds.
333
+            return null;
334
+        }
335
+
336
+        return (string) $versions[$offset + $delta];
337
+    }
338
+
339
+    /**
340
+     * @return string
341
+     */
342
+    private function getCurrentVersion() {
343
+        $m = $this->getMigratedVersions();
344
+        if (count($m) === 0) {
345
+            return '0';
346
+        }
347
+        $migrations = array_values($m);
348
+        return @end($migrations);
349
+    }
350
+
351
+    /**
352
+     * @param string $version
353
+     * @return string
354
+     * @throws \InvalidArgumentException
355
+     */
356
+    private function getClass($version) {
357
+        $this->ensureMigrationsAreLoaded();
358
+
359
+        if (isset($this->migrations[$version])) {
360
+            return $this->migrations[$version];
361
+        }
362
+
363
+        throw new \InvalidArgumentException("Version $version is unknown.");
364
+    }
365
+
366
+    /**
367
+     * Allows to set an IOutput implementation which is used for logging progress and messages
368
+     *
369
+     * @param IOutput $output
370
+     */
371
+    public function setOutput(IOutput $output) {
372
+        $this->output = $output;
373
+    }
374
+
375
+    /**
376
+     * Applies all not yet applied versions up to $to
377
+     *
378
+     * @param string $to
379
+     * @throws \InvalidArgumentException
380
+     */
381
+    public function migrate($to = 'latest') {
382
+        // read known migrations
383
+        $toBeExecuted = $this->getMigrationsToExecute($to);
384
+        foreach ($toBeExecuted as $version) {
385
+            $this->executeStep($version);
386
+        }
387
+    }
388
+
389
+    /**
390
+     * Get the human readable descriptions for the migration steps to run
391
+     *
392
+     * @param string $to
393
+     * @return string[] [$name => $description]
394
+     */
395
+    public function describeMigrationStep($to = 'latest') {
396
+        $toBeExecuted = $this->getMigrationsToExecute($to);
397
+        $description = [];
398
+        foreach ($toBeExecuted as $version) {
399
+            $migration = $this->createInstance($version);
400
+            if ($migration->name()) {
401
+                $description[$migration->name()] = $migration->description();
402
+            }
403
+        }
404
+        return $description;
405
+    }
406
+
407
+    /**
408
+     * @param string $version
409
+     * @return IMigrationStep
410
+     * @throws \InvalidArgumentException
411
+     */
412
+    protected function createInstance($version) {
413
+        $class = $this->getClass($version);
414
+        try {
415
+            $s = \OC::$server->query($class);
416
+
417
+            if (!$s instanceof IMigrationStep) {
418
+                throw new \InvalidArgumentException('Not a valid migration');
419
+            }
420
+        } catch (QueryException $e) {
421
+            if (class_exists($class)) {
422
+                $s = new $class();
423
+            } else {
424
+                throw new \InvalidArgumentException("Migration step '$class' is unknown");
425
+            }
426
+        }
427
+
428
+        return $s;
429
+    }
430
+
431
+    /**
432
+     * Executes one explicit version
433
+     *
434
+     * @param string $version
435
+     * @throws \InvalidArgumentException
436
+     */
437
+    public function executeStep($version) {
438
+        $instance = $this->createInstance($version);
439
+
440
+        $instance->preSchemaChange($this->output, function() {
441
+            return new SchemaWrapper($this->connection);
442
+        }, ['tablePrefix' => $this->connection->getPrefix()]);
443
+
444
+        $toSchema = $instance->changeSchema($this->output, function() {
445
+            return new SchemaWrapper($this->connection);
446
+        }, ['tablePrefix' => $this->connection->getPrefix()]);
447
+
448
+        if ($toSchema instanceof SchemaWrapper) {
449
+            $this->connection->migrateToSchema($toSchema->getWrappedSchema());
450
+            $toSchema->performDropTableCalls();
451
+        }
452
+
453
+        $instance->postSchemaChange($this->output, function() {
454
+            return new SchemaWrapper($this->connection);
455
+        }, ['tablePrefix' => $this->connection->getPrefix()]);
456
+
457
+        $this->markAsExecuted($version);
458
+    }
459
+
460
+    private function ensureMigrationsAreLoaded() {
461
+        if (empty($this->migrations)) {
462
+            $this->migrations = $this->findMigrations();
463
+        }
464
+    }
465 465
 }
Please login to merge, or discard this patch.
core/Command/Db/Migrations/StatusCommand.php 2 patches
Indentation   +88 added lines, -88 removed lines patch added patch discarded remove patch
@@ -32,94 +32,94 @@
 block discarded – undo
32 32
 
33 33
 class StatusCommand extends Command {
34 34
 
35
-	/** @var IDBConnection */
36
-	private $connection;
37
-
38
-	/**
39
-	 * @param IDBConnection $connection
40
-	 */
41
-	public function __construct(IDBConnection $connection) {
42
-		$this->connection = $connection;
43
-		parent::__construct();
44
-	}
45
-
46
-	protected function configure() {
47
-		$this
48
-			->setName('migrations:status')
49
-			->setDescription('View the status of a set of migrations.')
50
-			->addArgument('app', InputArgument::REQUIRED, 'Name of the app this migration command shall work on');
51
-	}
52
-
53
-	public function execute(InputInterface $input, OutputInterface $output) {
54
-		$appName = $input->getArgument('app');
55
-		$ms = new MigrationService($appName, $this->connection, new ConsoleOutput($output));
56
-
57
-		$infos = $this->getMigrationsInfos($ms);
58
-		foreach ($infos as $key => $value) {
59
-			if (is_array($value)) {
60
-				$output->writeln("    <comment>>></comment> $key:");
61
-				foreach ($value as $subKey => $subValue) {
62
-					$output->writeln("        <comment>>></comment> $subKey: " . str_repeat(' ', 46 - strlen($subKey)) . $subValue);
63
-				}
64
-			} else {
65
-				$output->writeln("    <comment>>></comment> $key: " . str_repeat(' ', 50 - strlen($key)) . $value);
66
-			}
67
-		}
68
-	}
69
-
70
-	/**
71
-	 * @param MigrationService $ms
72
-	 * @return array associative array of human readable info name as key and the actual information as value
73
-	 */
74
-	public function getMigrationsInfos(MigrationService $ms) {
75
-
76
-		$executedMigrations = $ms->getMigratedVersions();
77
-		$availableMigrations = $ms->getAvailableVersions();
78
-		$executedUnavailableMigrations = array_diff($executedMigrations, array_keys($availableMigrations));
79
-
80
-		$numExecutedUnavailableMigrations = count($executedUnavailableMigrations);
81
-		$numNewMigrations = count(array_diff(array_keys($availableMigrations), $executedMigrations));
82
-		$pending = $ms->describeMigrationStep('lastest');
83
-
84
-		$infos = [
85
-			'App'								=> $ms->getApp(),
86
-			'Version Table Name'				=> $ms->getMigrationsTableName(),
87
-			'Migrations Namespace'				=> $ms->getMigrationsNamespace(),
88
-			'Migrations Directory'				=> $ms->getMigrationsDirectory(),
89
-			'Previous Version'					=> $this->getFormattedVersionAlias($ms, 'prev'),
90
-			'Current Version'					=> $this->getFormattedVersionAlias($ms, 'current'),
91
-			'Next Version'						=> $this->getFormattedVersionAlias($ms, 'next'),
92
-			'Latest Version'					=> $this->getFormattedVersionAlias($ms, 'latest'),
93
-			'Executed Migrations'				=> count($executedMigrations),
94
-			'Executed Unavailable Migrations'	=> $numExecutedUnavailableMigrations,
95
-			'Available Migrations'				=> count($availableMigrations),
96
-			'New Migrations'					=> $numNewMigrations,
97
-			'Pending Migrations'				=> count($pending) ? $pending : 'None'
98
-		];
99
-
100
-		return $infos;
101
-	}
102
-
103
-	/**
104
-	 * @param MigrationService $migrationService
105
-	 * @param string $alias
106
-	 * @return mixed|null|string
107
-	 */
108
-	private function getFormattedVersionAlias(MigrationService $migrationService, $alias) {
109
-		$migration = $migrationService->getMigration($alias);
110
-		//No version found
111
-		if ($migration === null) {
112
-			if ($alias === 'next') {
113
-				return 'Already at latest migration step';
114
-			}
115
-
116
-			if ($alias === 'prev') {
117
-				return 'Already at first migration step';
118
-			}
119
-		}
120
-
121
-		return $migration;
122
-	}
35
+    /** @var IDBConnection */
36
+    private $connection;
37
+
38
+    /**
39
+     * @param IDBConnection $connection
40
+     */
41
+    public function __construct(IDBConnection $connection) {
42
+        $this->connection = $connection;
43
+        parent::__construct();
44
+    }
45
+
46
+    protected function configure() {
47
+        $this
48
+            ->setName('migrations:status')
49
+            ->setDescription('View the status of a set of migrations.')
50
+            ->addArgument('app', InputArgument::REQUIRED, 'Name of the app this migration command shall work on');
51
+    }
52
+
53
+    public function execute(InputInterface $input, OutputInterface $output) {
54
+        $appName = $input->getArgument('app');
55
+        $ms = new MigrationService($appName, $this->connection, new ConsoleOutput($output));
56
+
57
+        $infos = $this->getMigrationsInfos($ms);
58
+        foreach ($infos as $key => $value) {
59
+            if (is_array($value)) {
60
+                $output->writeln("    <comment>>></comment> $key:");
61
+                foreach ($value as $subKey => $subValue) {
62
+                    $output->writeln("        <comment>>></comment> $subKey: " . str_repeat(' ', 46 - strlen($subKey)) . $subValue);
63
+                }
64
+            } else {
65
+                $output->writeln("    <comment>>></comment> $key: " . str_repeat(' ', 50 - strlen($key)) . $value);
66
+            }
67
+        }
68
+    }
69
+
70
+    /**
71
+     * @param MigrationService $ms
72
+     * @return array associative array of human readable info name as key and the actual information as value
73
+     */
74
+    public function getMigrationsInfos(MigrationService $ms) {
75
+
76
+        $executedMigrations = $ms->getMigratedVersions();
77
+        $availableMigrations = $ms->getAvailableVersions();
78
+        $executedUnavailableMigrations = array_diff($executedMigrations, array_keys($availableMigrations));
79
+
80
+        $numExecutedUnavailableMigrations = count($executedUnavailableMigrations);
81
+        $numNewMigrations = count(array_diff(array_keys($availableMigrations), $executedMigrations));
82
+        $pending = $ms->describeMigrationStep('lastest');
83
+
84
+        $infos = [
85
+            'App'								=> $ms->getApp(),
86
+            'Version Table Name'				=> $ms->getMigrationsTableName(),
87
+            'Migrations Namespace'				=> $ms->getMigrationsNamespace(),
88
+            'Migrations Directory'				=> $ms->getMigrationsDirectory(),
89
+            'Previous Version'					=> $this->getFormattedVersionAlias($ms, 'prev'),
90
+            'Current Version'					=> $this->getFormattedVersionAlias($ms, 'current'),
91
+            'Next Version'						=> $this->getFormattedVersionAlias($ms, 'next'),
92
+            'Latest Version'					=> $this->getFormattedVersionAlias($ms, 'latest'),
93
+            'Executed Migrations'				=> count($executedMigrations),
94
+            'Executed Unavailable Migrations'	=> $numExecutedUnavailableMigrations,
95
+            'Available Migrations'				=> count($availableMigrations),
96
+            'New Migrations'					=> $numNewMigrations,
97
+            'Pending Migrations'				=> count($pending) ? $pending : 'None'
98
+        ];
99
+
100
+        return $infos;
101
+    }
102
+
103
+    /**
104
+     * @param MigrationService $migrationService
105
+     * @param string $alias
106
+     * @return mixed|null|string
107
+     */
108
+    private function getFormattedVersionAlias(MigrationService $migrationService, $alias) {
109
+        $migration = $migrationService->getMigration($alias);
110
+        //No version found
111
+        if ($migration === null) {
112
+            if ($alias === 'next') {
113
+                return 'Already at latest migration step';
114
+            }
115
+
116
+            if ($alias === 'prev') {
117
+                return 'Already at first migration step';
118
+            }
119
+        }
120
+
121
+        return $migration;
122
+    }
123 123
 
124 124
 
125 125
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -59,10 +59,10 @@
 block discarded – undo
59 59
 			if (is_array($value)) {
60 60
 				$output->writeln("    <comment>>></comment> $key:");
61 61
 				foreach ($value as $subKey => $subValue) {
62
-					$output->writeln("        <comment>>></comment> $subKey: " . str_repeat(' ', 46 - strlen($subKey)) . $subValue);
62
+					$output->writeln("        <comment>>></comment> $subKey: ".str_repeat(' ', 46 - strlen($subKey)).$subValue);
63 63
 				}
64 64
 			} else {
65
-				$output->writeln("    <comment>>></comment> $key: " . str_repeat(' ', 50 - strlen($key)) . $value);
65
+				$output->writeln("    <comment>>></comment> $key: ".str_repeat(' ', 50 - strlen($key)).$value);
66 66
 			}
67 67
 		}
68 68
 	}
Please login to merge, or discard this patch.
core/Migrations/Version14000Date20180404140050.php 1 patch
Indentation   +42 added lines, -42 removed lines patch added patch discarded remove patch
@@ -34,55 +34,55 @@
 block discarded – undo
34 34
  */
35 35
 class Version14000Date20180404140050 extends SimpleMigrationStep {
36 36
 
37
-	/** @var IDBConnection */
38
-	private $connection;
37
+    /** @var IDBConnection */
38
+    private $connection;
39 39
 
40
-	public function __construct(IDBConnection $connection) {
41
-		$this->connection = $connection;
42
-	}
40
+    public function __construct(IDBConnection $connection) {
41
+        $this->connection = $connection;
42
+    }
43 43
 
44
-	public function name(): string {
45
-		return 'Add lowercase user id column to users table';
46
-	}
44
+    public function name(): string {
45
+        return 'Add lowercase user id column to users table';
46
+    }
47 47
 
48
-	public function description(): string {
49
-		return 'Adds "uid_lower" column to the users table and fills the column to allow indexed case-insensitive searches';
50
-	}
48
+    public function description(): string {
49
+        return 'Adds "uid_lower" column to the users table and fills the column to allow indexed case-insensitive searches';
50
+    }
51 51
 
52
-	/**
53
-	 * @param IOutput $output
54
-	 * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
55
-	 * @param array $options
56
-	 * @return null|ISchemaWrapper
57
-	 */
58
-	public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
59
-		/** @var ISchemaWrapper $schema */
60
-		$schema = $schemaClosure();
52
+    /**
53
+     * @param IOutput $output
54
+     * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
55
+     * @param array $options
56
+     * @return null|ISchemaWrapper
57
+     */
58
+    public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
59
+        /** @var ISchemaWrapper $schema */
60
+        $schema = $schemaClosure();
61 61
 
62
-		$table = $schema->getTable('users');
62
+        $table = $schema->getTable('users');
63 63
 
64
-		$table->addColumn('uid_lower', 'string', [
65
-			'notnull' => false,
66
-			'length' => 64,
67
-			'default' => '',
68
-		]);
69
-		$table->addIndex(['uid_lower'], 'user_uid_lower');
64
+        $table->addColumn('uid_lower', 'string', [
65
+            'notnull' => false,
66
+            'length' => 64,
67
+            'default' => '',
68
+        ]);
69
+        $table->addIndex(['uid_lower'], 'user_uid_lower');
70 70
 
71
-		return $schema;
72
-	}
71
+        return $schema;
72
+    }
73 73
 
74
-	/**
75
-	 * @param IOutput $output
76
-	 * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
77
-	 * @param array $options
78
-	 *
79
-	 * @suppress SqlInjectionChecker
80
-	 */
81
-	public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
82
-		$qb = $this->connection->getQueryBuilder();
74
+    /**
75
+     * @param IOutput $output
76
+     * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
77
+     * @param array $options
78
+     *
79
+     * @suppress SqlInjectionChecker
80
+     */
81
+    public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
82
+        $qb = $this->connection->getQueryBuilder();
83 83
 
84
-		$qb->update('users')
85
-			->set('uid_lower', $qb->func()->lower('uid'));
86
-		$qb->execute();
87
-	}
84
+        $qb->update('users')
85
+            ->set('uid_lower', $qb->func()->lower('uid'));
86
+        $qb->execute();
87
+    }
88 88
 }
Please login to merge, or discard this patch.
core/Migrations/Version14000Date20180129121024.php 1 patch
Indentation   +22 added lines, -22 removed lines patch added patch discarded remove patch
@@ -30,30 +30,30 @@
 block discarded – undo
30 30
  * Delete the admin|personal sections and settings tables
31 31
  */
32 32
 class Version14000Date20180129121024 extends SimpleMigrationStep {
33
-	public function name(): string {
34
-		return 'Drop obsolete settings tables';
35
-	}
33
+    public function name(): string {
34
+        return 'Drop obsolete settings tables';
35
+    }
36 36
 
37
-	public function description(): string {
38
-		return 'Drops the following obsolete tables: "admin_sections", "admin_settings", "personal_sections" and "personal_settings"';
39
-	}
37
+    public function description(): string {
38
+        return 'Drops the following obsolete tables: "admin_sections", "admin_settings", "personal_sections" and "personal_settings"';
39
+    }
40 40
 
41
-	/**
42
-	 * @param IOutput $output
43
-	 * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
44
-	 * @param array $options
45
-	 * @return null|ISchemaWrapper
46
-	 * @since 13.0.0
47
-	 */
48
-	public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
49
-		/** @var ISchemaWrapper $schema */
50
-		$schema = $schemaClosure();
41
+    /**
42
+     * @param IOutput $output
43
+     * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
44
+     * @param array $options
45
+     * @return null|ISchemaWrapper
46
+     * @since 13.0.0
47
+     */
48
+    public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
49
+        /** @var ISchemaWrapper $schema */
50
+        $schema = $schemaClosure();
51 51
 
52
-		$schema->dropTable('admin_sections');
53
-		$schema->dropTable('admin_settings');
54
-		$schema->dropTable('personal_sections');
55
-		$schema->dropTable('personal_settings');
52
+        $schema->dropTable('admin_sections');
53
+        $schema->dropTable('admin_settings');
54
+        $schema->dropTable('personal_sections');
55
+        $schema->dropTable('personal_settings');
56 56
 
57
-		return $schema;
58
-	}
57
+        return $schema;
58
+    }
59 59
 }
Please login to merge, or discard this patch.
lib/public/Migration/SimpleMigrationStep.php 1 patch
Indentation   +42 added lines, -42 removed lines patch added patch discarded remove patch
@@ -29,50 +29,50 @@
 block discarded – undo
29 29
  * @since 13.0.0
30 30
  */
31 31
 abstract class SimpleMigrationStep implements IMigrationStep {
32
-	/**
33
-	 * Human readable name of the migration step
34
-	 *
35
-	 * @return string
36
-	 */
37
-	public function name(): string {
38
-		return '';
39
-	}
32
+    /**
33
+     * Human readable name of the migration step
34
+     *
35
+     * @return string
36
+     */
37
+    public function name(): string {
38
+        return '';
39
+    }
40 40
 
41
-	/**
42
-	 * Human readable description of the migration step
43
-	 *
44
-	 * @return string
45
-	 */
46
-	public function description(): string {
47
-		return '';
48
-	}
41
+    /**
42
+     * Human readable description of the migration step
43
+     *
44
+     * @return string
45
+     */
46
+    public function description(): string {
47
+        return '';
48
+    }
49 49
 
50
-	/**
51
-	 * @param IOutput $output
52
-	 * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
53
-	 * @param array $options
54
-	 * @since 13.0.0
55
-	 */
56
-	public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
57
-	}
50
+    /**
51
+     * @param IOutput $output
52
+     * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
53
+     * @param array $options
54
+     * @since 13.0.0
55
+     */
56
+    public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
57
+    }
58 58
 
59
-	/**
60
-	 * @param IOutput $output
61
-	 * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
62
-	 * @param array $options
63
-	 * @return null|ISchemaWrapper
64
-	 * @since 13.0.0
65
-	 */
66
-	public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
67
-		return null;
68
-	}
59
+    /**
60
+     * @param IOutput $output
61
+     * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
62
+     * @param array $options
63
+     * @return null|ISchemaWrapper
64
+     * @since 13.0.0
65
+     */
66
+    public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
67
+        return null;
68
+    }
69 69
 
70
-	/**
71
-	 * @param IOutput $output
72
-	 * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
73
-	 * @param array $options
74
-	 * @since 13.0.0
75
-	 */
76
-	public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
77
-	}
70
+    /**
71
+     * @param IOutput $output
72
+     * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
73
+     * @param array $options
74
+     * @since 13.0.0
75
+     */
76
+    public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
77
+    }
78 78
 }
Please login to merge, or discard this patch.