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