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