Passed
Push — master ( f31d24...46872e )
by Roeland
78:38 queued 65:22
created
lib/private/DB/MigrationService.php 2 patches
Indentation   +603 added lines, -603 removed lines patch added patch discarded remove patch
@@ -47,607 +47,607 @@
 block discarded – undo
47 47
 
48 48
 class MigrationService {
49 49
 
50
-	/** @var boolean */
51
-	private $migrationTableCreated;
52
-	/** @var array */
53
-	private $migrations;
54
-	/** @var IOutput */
55
-	private $output;
56
-	/** @var Connection */
57
-	private $connection;
58
-	/** @var string */
59
-	private $appName;
60
-	/** @var bool */
61
-	private $checkOracle;
62
-
63
-	/**
64
-	 * MigrationService constructor.
65
-	 *
66
-	 * @param $appName
67
-	 * @param Connection $connection
68
-	 * @param AppLocator $appLocator
69
-	 * @param IOutput|null $output
70
-	 * @throws \Exception
71
-	 */
72
-	public function __construct($appName, Connection $connection, IOutput $output = null, AppLocator $appLocator = null) {
73
-		$this->appName = $appName;
74
-		$this->connection = $connection;
75
-		$this->output = $output;
76
-		if (null === $this->output) {
77
-			$this->output = new SimpleOutput(\OC::$server->getLogger(), $appName);
78
-		}
79
-
80
-		if ($appName === 'core') {
81
-			$this->migrationsPath = \OC::$SERVERROOT . '/core/Migrations';
82
-			$this->migrationsNamespace = 'OC\\Core\\Migrations';
83
-			$this->checkOracle = true;
84
-		} else {
85
-			if (null === $appLocator) {
86
-				$appLocator = new AppLocator();
87
-			}
88
-			$appPath = $appLocator->getAppPath($appName);
89
-			$namespace = App::buildAppNamespace($appName);
90
-			$this->migrationsPath = "$appPath/lib/Migration";
91
-			$this->migrationsNamespace = $namespace . '\\Migration';
92
-
93
-			$infoParser = new InfoParser();
94
-			$info = $infoParser->parse($appPath . '/appinfo/info.xml');
95
-			if (!isset($info['dependencies']['database'])) {
96
-				$this->checkOracle = true;
97
-			} else {
98
-				$this->checkOracle = false;
99
-				foreach ($info['dependencies']['database'] as $database) {
100
-					if (\is_string($database) && $database === 'oci') {
101
-						$this->checkOracle = true;
102
-					} elseif (\is_array($database) && isset($database['@value']) && $database['@value'] === 'oci') {
103
-						$this->checkOracle = true;
104
-					}
105
-				}
106
-			}
107
-		}
108
-	}
109
-
110
-	/**
111
-	 * Returns the name of the app for which this migration is executed
112
-	 *
113
-	 * @return string
114
-	 */
115
-	public function getApp() {
116
-		return $this->appName;
117
-	}
118
-
119
-	/**
120
-	 * @return bool
121
-	 * @codeCoverageIgnore - this will implicitly tested on installation
122
-	 */
123
-	private function createMigrationTable() {
124
-		if ($this->migrationTableCreated) {
125
-			return false;
126
-		}
127
-
128
-		if ($this->connection->tableExists('migrations') && \OC::$server->getConfig()->getAppValue('core', 'vendor', '') !== 'owncloud') {
129
-			$this->migrationTableCreated = true;
130
-			return false;
131
-		}
132
-
133
-		$schema = new SchemaWrapper($this->connection);
134
-
135
-		/**
136
-		 * We drop the table when it has different columns or the definition does not
137
-		 * match. E.g. ownCloud uses a length of 177 for app and 14 for version.
138
-		 */
139
-		try {
140
-			$table = $schema->getTable('migrations');
141
-			$columns = $table->getColumns();
142
-
143
-			if (count($columns) === 2) {
144
-				try {
145
-					$column = $table->getColumn('app');
146
-					$schemaMismatch = $column->getLength() !== 255;
147
-
148
-					if (!$schemaMismatch) {
149
-						$column = $table->getColumn('version');
150
-						$schemaMismatch = $column->getLength() !== 255;
151
-					}
152
-				} catch (SchemaException $e) {
153
-					// One of the columns is missing
154
-					$schemaMismatch = true;
155
-				}
156
-
157
-				if (!$schemaMismatch) {
158
-					// Table exists and schema matches: return back!
159
-					$this->migrationTableCreated = true;
160
-					return false;
161
-				}
162
-			}
163
-
164
-			// Drop the table, when it didn't match our expectations.
165
-			$this->connection->dropTable('migrations');
166
-
167
-			// Recreate the schema after the table was dropped.
168
-			$schema = new SchemaWrapper($this->connection);
169
-		} catch (SchemaException $e) {
170
-			// Table not found, no need to panic, we will create it.
171
-		}
172
-
173
-		$table = $schema->createTable('migrations');
174
-		$table->addColumn('app', Types::STRING, ['length' => 255]);
175
-		$table->addColumn('version', Types::STRING, ['length' => 255]);
176
-		$table->setPrimaryKey(['app', 'version']);
177
-
178
-		$this->connection->migrateToSchema($schema->getWrappedSchema());
179
-
180
-		$this->migrationTableCreated = true;
181
-
182
-		return true;
183
-	}
184
-
185
-	/**
186
-	 * Returns all versions which have already been applied
187
-	 *
188
-	 * @return string[]
189
-	 * @codeCoverageIgnore - no need to test this
190
-	 */
191
-	public function getMigratedVersions() {
192
-		$this->createMigrationTable();
193
-		$qb = $this->connection->getQueryBuilder();
194
-
195
-		$qb->select('version')
196
-			->from('migrations')
197
-			->where($qb->expr()->eq('app', $qb->createNamedParameter($this->getApp())))
198
-			->orderBy('version');
199
-
200
-		$result = $qb->execute();
201
-		$rows = $result->fetchAll(\PDO::FETCH_COLUMN);
202
-		$result->closeCursor();
203
-
204
-		return $rows;
205
-	}
206
-
207
-	/**
208
-	 * Returns all versions which are available in the migration folder
209
-	 *
210
-	 * @return array
211
-	 */
212
-	public function getAvailableVersions() {
213
-		$this->ensureMigrationsAreLoaded();
214
-		return array_map('strval', array_keys($this->migrations));
215
-	}
216
-
217
-	protected function findMigrations() {
218
-		$directory = realpath($this->migrationsPath);
219
-		if ($directory === false || !file_exists($directory) || !is_dir($directory)) {
220
-			return [];
221
-		}
222
-
223
-		$iterator = new \RegexIterator(
224
-			new \RecursiveIteratorIterator(
225
-				new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS),
226
-				\RecursiveIteratorIterator::LEAVES_ONLY
227
-			),
228
-			'#^.+\\/Version[^\\/]{1,255}\\.php$#i',
229
-			\RegexIterator::GET_MATCH);
230
-
231
-		$files = array_keys(iterator_to_array($iterator));
232
-		uasort($files, function ($a, $b) {
233
-			preg_match('/^Version(\d+)Date(\d+)\\.php$/', basename($a), $matchA);
234
-			preg_match('/^Version(\d+)Date(\d+)\\.php$/', basename($b), $matchB);
235
-			if (!empty($matchA) && !empty($matchB)) {
236
-				if ($matchA[1] !== $matchB[1]) {
237
-					return ($matchA[1] < $matchB[1]) ? -1 : 1;
238
-				}
239
-				return ($matchA[2] < $matchB[2]) ? -1 : 1;
240
-			}
241
-			return (basename($a) < basename($b)) ? -1 : 1;
242
-		});
243
-
244
-		$migrations = [];
245
-
246
-		foreach ($files as $file) {
247
-			$className = basename($file, '.php');
248
-			$version = (string) substr($className, 7);
249
-			if ($version === '0') {
250
-				throw new \InvalidArgumentException(
251
-					"Cannot load a migrations with the name '$version' because it is a reserved number"
252
-				);
253
-			}
254
-			$migrations[$version] = sprintf('%s\\%s', $this->migrationsNamespace, $className);
255
-		}
256
-
257
-		return $migrations;
258
-	}
259
-
260
-	/**
261
-	 * @param string $to
262
-	 * @return string[]
263
-	 */
264
-	private function getMigrationsToExecute($to) {
265
-		$knownMigrations = $this->getMigratedVersions();
266
-		$availableMigrations = $this->getAvailableVersions();
267
-
268
-		$toBeExecuted = [];
269
-		foreach ($availableMigrations as $v) {
270
-			if ($to !== 'latest' && $v > $to) {
271
-				continue;
272
-			}
273
-			if ($this->shallBeExecuted($v, $knownMigrations)) {
274
-				$toBeExecuted[] = $v;
275
-			}
276
-		}
277
-
278
-		return $toBeExecuted;
279
-	}
280
-
281
-	/**
282
-	 * @param string $m
283
-	 * @param string[] $knownMigrations
284
-	 * @return bool
285
-	 */
286
-	private function shallBeExecuted($m, $knownMigrations) {
287
-		if (in_array($m, $knownMigrations)) {
288
-			return false;
289
-		}
290
-
291
-		return true;
292
-	}
293
-
294
-	/**
295
-	 * @param string $version
296
-	 */
297
-	private function markAsExecuted($version) {
298
-		$this->connection->insertIfNotExist('*PREFIX*migrations', [
299
-			'app' => $this->appName,
300
-			'version' => $version
301
-		]);
302
-	}
303
-
304
-	/**
305
-	 * Returns the name of the table which holds the already applied versions
306
-	 *
307
-	 * @return string
308
-	 */
309
-	public function getMigrationsTableName() {
310
-		return $this->connection->getPrefix() . 'migrations';
311
-	}
312
-
313
-	/**
314
-	 * Returns the namespace of the version classes
315
-	 *
316
-	 * @return string
317
-	 */
318
-	public function getMigrationsNamespace() {
319
-		return $this->migrationsNamespace;
320
-	}
321
-
322
-	/**
323
-	 * Returns the directory which holds the versions
324
-	 *
325
-	 * @return string
326
-	 */
327
-	public function getMigrationsDirectory() {
328
-		return $this->migrationsPath;
329
-	}
330
-
331
-	/**
332
-	 * Return the explicit version for the aliases; current, next, prev, latest
333
-	 *
334
-	 * @param string $alias
335
-	 * @return mixed|null|string
336
-	 */
337
-	public function getMigration($alias) {
338
-		switch ($alias) {
339
-			case 'current':
340
-				return $this->getCurrentVersion();
341
-			case 'next':
342
-				return $this->getRelativeVersion($this->getCurrentVersion(), 1);
343
-			case 'prev':
344
-				return $this->getRelativeVersion($this->getCurrentVersion(), -1);
345
-			case 'latest':
346
-				$this->ensureMigrationsAreLoaded();
347
-
348
-				$migrations = $this->getAvailableVersions();
349
-				return @end($migrations);
350
-		}
351
-		return '0';
352
-	}
353
-
354
-	/**
355
-	 * @param string $version
356
-	 * @param int $delta
357
-	 * @return null|string
358
-	 */
359
-	private function getRelativeVersion($version, $delta) {
360
-		$this->ensureMigrationsAreLoaded();
361
-
362
-		$versions = $this->getAvailableVersions();
363
-		array_unshift($versions, 0);
364
-		$offset = array_search($version, $versions, true);
365
-		if ($offset === false || !isset($versions[$offset + $delta])) {
366
-			// Unknown version or delta out of bounds.
367
-			return null;
368
-		}
369
-
370
-		return (string) $versions[$offset + $delta];
371
-	}
372
-
373
-	/**
374
-	 * @return string
375
-	 */
376
-	private function getCurrentVersion() {
377
-		$m = $this->getMigratedVersions();
378
-		if (count($m) === 0) {
379
-			return '0';
380
-		}
381
-		$migrations = array_values($m);
382
-		return @end($migrations);
383
-	}
384
-
385
-	/**
386
-	 * @param string $version
387
-	 * @return string
388
-	 * @throws \InvalidArgumentException
389
-	 */
390
-	private function getClass($version) {
391
-		$this->ensureMigrationsAreLoaded();
392
-
393
-		if (isset($this->migrations[$version])) {
394
-			return $this->migrations[$version];
395
-		}
396
-
397
-		throw new \InvalidArgumentException("Version $version is unknown.");
398
-	}
399
-
400
-	/**
401
-	 * Allows to set an IOutput implementation which is used for logging progress and messages
402
-	 *
403
-	 * @param IOutput $output
404
-	 */
405
-	public function setOutput(IOutput $output) {
406
-		$this->output = $output;
407
-	}
408
-
409
-	/**
410
-	 * Applies all not yet applied versions up to $to
411
-	 *
412
-	 * @param string $to
413
-	 * @param bool $schemaOnly
414
-	 * @throws \InvalidArgumentException
415
-	 */
416
-	public function migrate($to = 'latest', $schemaOnly = false) {
417
-		if ($schemaOnly) {
418
-			$this->migrateSchemaOnly($to);
419
-			return;
420
-		}
421
-
422
-		// read known migrations
423
-		$toBeExecuted = $this->getMigrationsToExecute($to);
424
-		foreach ($toBeExecuted as $version) {
425
-			try {
426
-				$this->executeStep($version, $schemaOnly);
427
-			} catch (DriverException $e) {
428
-				// The exception itself does not contain the name of the migration,
429
-				// so we wrap it here, to make debugging easier.
430
-				throw new \Exception('Database error when running migration ' . $to . ' for app ' . $this->getApp(), 0, $e);
431
-			}
432
-		}
433
-	}
434
-
435
-	/**
436
-	 * Applies all not yet applied versions up to $to
437
-	 *
438
-	 * @param string $to
439
-	 * @throws \InvalidArgumentException
440
-	 */
441
-	public function migrateSchemaOnly($to = 'latest') {
442
-		// read known migrations
443
-		$toBeExecuted = $this->getMigrationsToExecute($to);
444
-
445
-		if (empty($toBeExecuted)) {
446
-			return;
447
-		}
448
-
449
-		$toSchema = null;
450
-		foreach ($toBeExecuted as $version) {
451
-			$instance = $this->createInstance($version);
452
-
453
-			$toSchema = $instance->changeSchema($this->output, function () use ($toSchema) {
454
-				return $toSchema ?: new SchemaWrapper($this->connection);
455
-			}, ['tablePrefix' => $this->connection->getPrefix()]) ?: $toSchema;
456
-		}
457
-
458
-		if ($toSchema instanceof SchemaWrapper) {
459
-			$targetSchema = $toSchema->getWrappedSchema();
460
-			if ($this->checkOracle) {
461
-				$beforeSchema = $this->connection->createSchema();
462
-				$this->ensureOracleConstraints($beforeSchema, $targetSchema, strlen($this->connection->getPrefix()));
463
-			}
464
-			$this->connection->migrateToSchema($targetSchema);
465
-			$toSchema->performDropTableCalls();
466
-		}
467
-
468
-		foreach ($toBeExecuted as $version) {
469
-			$this->markAsExecuted($version);
470
-		}
471
-	}
472
-
473
-	/**
474
-	 * Get the human readable descriptions for the migration steps to run
475
-	 *
476
-	 * @param string $to
477
-	 * @return string[] [$name => $description]
478
-	 */
479
-	public function describeMigrationStep($to = 'latest') {
480
-		$toBeExecuted = $this->getMigrationsToExecute($to);
481
-		$description = [];
482
-		foreach ($toBeExecuted as $version) {
483
-			$migration = $this->createInstance($version);
484
-			if ($migration->name()) {
485
-				$description[$migration->name()] = $migration->description();
486
-			}
487
-		}
488
-		return $description;
489
-	}
490
-
491
-	/**
492
-	 * @param string $version
493
-	 * @return IMigrationStep
494
-	 * @throws \InvalidArgumentException
495
-	 */
496
-	protected function createInstance($version) {
497
-		$class = $this->getClass($version);
498
-		try {
499
-			$s = \OC::$server->query($class);
500
-
501
-			if (!$s instanceof IMigrationStep) {
502
-				throw new \InvalidArgumentException('Not a valid migration');
503
-			}
504
-		} catch (QueryException $e) {
505
-			if (class_exists($class)) {
506
-				$s = new $class();
507
-			} else {
508
-				throw new \InvalidArgumentException("Migration step '$class' is unknown");
509
-			}
510
-		}
511
-
512
-		return $s;
513
-	}
514
-
515
-	/**
516
-	 * Executes one explicit version
517
-	 *
518
-	 * @param string $version
519
-	 * @param bool $schemaOnly
520
-	 * @throws \InvalidArgumentException
521
-	 */
522
-	public function executeStep($version, $schemaOnly = false) {
523
-		$instance = $this->createInstance($version);
524
-
525
-		if (!$schemaOnly) {
526
-			$instance->preSchemaChange($this->output, function () {
527
-				return new SchemaWrapper($this->connection);
528
-			}, ['tablePrefix' => $this->connection->getPrefix()]);
529
-		}
530
-
531
-		$toSchema = $instance->changeSchema($this->output, function () {
532
-			return new SchemaWrapper($this->connection);
533
-		}, ['tablePrefix' => $this->connection->getPrefix()]);
534
-
535
-		if ($toSchema instanceof SchemaWrapper) {
536
-			$targetSchema = $toSchema->getWrappedSchema();
537
-			if ($this->checkOracle) {
538
-				$sourceSchema = $this->connection->createSchema();
539
-				$this->ensureOracleConstraints($sourceSchema, $targetSchema, strlen($this->connection->getPrefix()));
540
-			}
541
-			$this->connection->migrateToSchema($targetSchema);
542
-			$toSchema->performDropTableCalls();
543
-		}
544
-
545
-		if (!$schemaOnly) {
546
-			$instance->postSchemaChange($this->output, function () {
547
-				return new SchemaWrapper($this->connection);
548
-			}, ['tablePrefix' => $this->connection->getPrefix()]);
549
-		}
550
-
551
-		$this->markAsExecuted($version);
552
-	}
553
-
554
-	/**
555
-	 * Naming constraints:
556
-	 * - Tables names must be 30 chars or shorter (27 + oc_ prefix)
557
-	 * - Column names must be 30 chars or shorter
558
-	 * - Index names must be 30 chars or shorter
559
-	 * - Sequence names must be 30 chars or shorter
560
-	 * - Primary key names must be set or the table name 23 chars or shorter
561
-	 *
562
-	 * Data constraints:
563
-	 * - Columns with "NotNull" can not have empty string as default value
564
-	 * - Columns with "NotNull" can not have number 0 as default value
565
-	 * - Columns with type "bool" (which is in fact integer of length 1) can not be "NotNull" as it can not store 0/false
566
-	 *
567
-	 * @param Schema $sourceSchema
568
-	 * @param Schema $targetSchema
569
-	 * @param int $prefixLength
570
-	 * @throws \Doctrine\DBAL\Exception
571
-	 */
572
-	public function ensureOracleConstraints(Schema $sourceSchema, Schema $targetSchema, int $prefixLength) {
573
-		$sequences = $targetSchema->getSequences();
574
-
575
-		foreach ($targetSchema->getTables() as $table) {
576
-			try {
577
-				$sourceTable = $sourceSchema->getTable($table->getName());
578
-			} catch (SchemaException $e) {
579
-				if (\strlen($table->getName()) - $prefixLength > 27) {
580
-					throw new \InvalidArgumentException('Table name "' . $table->getName() . '" is too long.');
581
-				}
582
-				$sourceTable = null;
583
-			}
584
-
585
-			foreach ($table->getColumns() as $thing) {
586
-				if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && \strlen($thing->getName()) > 30) {
587
-					throw new \InvalidArgumentException('Column name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
588
-				}
589
-
590
-				if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && $thing->getNotnull() && $thing->getDefault() === ''
591
-					&& $sourceTable instanceof Table && !$sourceTable->hasColumn($thing->getName())) {
592
-					throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is NotNull, but has empty string or null as default.');
593
-				}
594
-
595
-				if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && $thing->getNotnull() && $thing->getType()->getName() === Types::BOOLEAN) {
596
-					throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is type Bool and also NotNull, so it can not store "false".');
597
-				}
598
-			}
599
-
600
-			foreach ($table->getIndexes() as $thing) {
601
-				if ((!$sourceTable instanceof Table || !$sourceTable->hasIndex($thing->getName())) && \strlen($thing->getName()) > 30) {
602
-					throw new \InvalidArgumentException('Index name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
603
-				}
604
-			}
605
-
606
-			foreach ($table->getForeignKeys() as $thing) {
607
-				if ((!$sourceTable instanceof Table || !$sourceTable->hasForeignKey($thing->getName())) && \strlen($thing->getName()) > 30) {
608
-					throw new \InvalidArgumentException('Foreign key name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
609
-				}
610
-			}
611
-
612
-			$primaryKey = $table->getPrimaryKey();
613
-			if ($primaryKey instanceof Index && (!$sourceTable instanceof Table || !$sourceTable->hasPrimaryKey())) {
614
-				$indexName = strtolower($primaryKey->getName());
615
-				$isUsingDefaultName = $indexName === 'primary';
616
-
617
-				if ($this->connection->getDatabasePlatform() instanceof PostgreSQL94Platform) {
618
-					$defaultName = $table->getName() . '_pkey';
619
-					$isUsingDefaultName = strtolower($defaultName) === $indexName;
620
-
621
-					if ($isUsingDefaultName) {
622
-						$sequenceName = $table->getName() . '_' . implode('_', $primaryKey->getColumns()) . '_seq';
623
-						$sequences = array_filter($sequences, function (Sequence $sequence) use ($sequenceName) {
624
-							return $sequence->getName() !== $sequenceName;
625
-						});
626
-					}
627
-				} elseif ($this->connection->getDatabasePlatform() instanceof OraclePlatform) {
628
-					$defaultName = $table->getName() . '_seq';
629
-					$isUsingDefaultName = strtolower($defaultName) === $indexName;
630
-				}
631
-
632
-				if (!$isUsingDefaultName && \strlen($indexName) > 30) {
633
-					throw new \InvalidArgumentException('Primary index name on "' . $table->getName() . '" is too long.');
634
-				}
635
-				if ($isUsingDefaultName && \strlen($table->getName()) - $prefixLength >= 23) {
636
-					throw new \InvalidArgumentException('Primary index name on "' . $table->getName() . '" is too long.');
637
-				}
638
-			}
639
-		}
640
-
641
-		foreach ($sequences as $sequence) {
642
-			if (!$sourceSchema->hasSequence($sequence->getName()) && \strlen($sequence->getName()) > 30) {
643
-				throw new \InvalidArgumentException('Sequence name "' . $sequence->getName() . '" is too long.');
644
-			}
645
-		}
646
-	}
647
-
648
-	private function ensureMigrationsAreLoaded() {
649
-		if (empty($this->migrations)) {
650
-			$this->migrations = $this->findMigrations();
651
-		}
652
-	}
50
+    /** @var boolean */
51
+    private $migrationTableCreated;
52
+    /** @var array */
53
+    private $migrations;
54
+    /** @var IOutput */
55
+    private $output;
56
+    /** @var Connection */
57
+    private $connection;
58
+    /** @var string */
59
+    private $appName;
60
+    /** @var bool */
61
+    private $checkOracle;
62
+
63
+    /**
64
+     * MigrationService constructor.
65
+     *
66
+     * @param $appName
67
+     * @param Connection $connection
68
+     * @param AppLocator $appLocator
69
+     * @param IOutput|null $output
70
+     * @throws \Exception
71
+     */
72
+    public function __construct($appName, Connection $connection, IOutput $output = null, AppLocator $appLocator = null) {
73
+        $this->appName = $appName;
74
+        $this->connection = $connection;
75
+        $this->output = $output;
76
+        if (null === $this->output) {
77
+            $this->output = new SimpleOutput(\OC::$server->getLogger(), $appName);
78
+        }
79
+
80
+        if ($appName === 'core') {
81
+            $this->migrationsPath = \OC::$SERVERROOT . '/core/Migrations';
82
+            $this->migrationsNamespace = 'OC\\Core\\Migrations';
83
+            $this->checkOracle = true;
84
+        } else {
85
+            if (null === $appLocator) {
86
+                $appLocator = new AppLocator();
87
+            }
88
+            $appPath = $appLocator->getAppPath($appName);
89
+            $namespace = App::buildAppNamespace($appName);
90
+            $this->migrationsPath = "$appPath/lib/Migration";
91
+            $this->migrationsNamespace = $namespace . '\\Migration';
92
+
93
+            $infoParser = new InfoParser();
94
+            $info = $infoParser->parse($appPath . '/appinfo/info.xml');
95
+            if (!isset($info['dependencies']['database'])) {
96
+                $this->checkOracle = true;
97
+            } else {
98
+                $this->checkOracle = false;
99
+                foreach ($info['dependencies']['database'] as $database) {
100
+                    if (\is_string($database) && $database === 'oci') {
101
+                        $this->checkOracle = true;
102
+                    } elseif (\is_array($database) && isset($database['@value']) && $database['@value'] === 'oci') {
103
+                        $this->checkOracle = true;
104
+                    }
105
+                }
106
+            }
107
+        }
108
+    }
109
+
110
+    /**
111
+     * Returns the name of the app for which this migration is executed
112
+     *
113
+     * @return string
114
+     */
115
+    public function getApp() {
116
+        return $this->appName;
117
+    }
118
+
119
+    /**
120
+     * @return bool
121
+     * @codeCoverageIgnore - this will implicitly tested on installation
122
+     */
123
+    private function createMigrationTable() {
124
+        if ($this->migrationTableCreated) {
125
+            return false;
126
+        }
127
+
128
+        if ($this->connection->tableExists('migrations') && \OC::$server->getConfig()->getAppValue('core', 'vendor', '') !== 'owncloud') {
129
+            $this->migrationTableCreated = true;
130
+            return false;
131
+        }
132
+
133
+        $schema = new SchemaWrapper($this->connection);
134
+
135
+        /**
136
+         * We drop the table when it has different columns or the definition does not
137
+         * match. E.g. ownCloud uses a length of 177 for app and 14 for version.
138
+         */
139
+        try {
140
+            $table = $schema->getTable('migrations');
141
+            $columns = $table->getColumns();
142
+
143
+            if (count($columns) === 2) {
144
+                try {
145
+                    $column = $table->getColumn('app');
146
+                    $schemaMismatch = $column->getLength() !== 255;
147
+
148
+                    if (!$schemaMismatch) {
149
+                        $column = $table->getColumn('version');
150
+                        $schemaMismatch = $column->getLength() !== 255;
151
+                    }
152
+                } catch (SchemaException $e) {
153
+                    // One of the columns is missing
154
+                    $schemaMismatch = true;
155
+                }
156
+
157
+                if (!$schemaMismatch) {
158
+                    // Table exists and schema matches: return back!
159
+                    $this->migrationTableCreated = true;
160
+                    return false;
161
+                }
162
+            }
163
+
164
+            // Drop the table, when it didn't match our expectations.
165
+            $this->connection->dropTable('migrations');
166
+
167
+            // Recreate the schema after the table was dropped.
168
+            $schema = new SchemaWrapper($this->connection);
169
+        } catch (SchemaException $e) {
170
+            // Table not found, no need to panic, we will create it.
171
+        }
172
+
173
+        $table = $schema->createTable('migrations');
174
+        $table->addColumn('app', Types::STRING, ['length' => 255]);
175
+        $table->addColumn('version', Types::STRING, ['length' => 255]);
176
+        $table->setPrimaryKey(['app', 'version']);
177
+
178
+        $this->connection->migrateToSchema($schema->getWrappedSchema());
179
+
180
+        $this->migrationTableCreated = true;
181
+
182
+        return true;
183
+    }
184
+
185
+    /**
186
+     * Returns all versions which have already been applied
187
+     *
188
+     * @return string[]
189
+     * @codeCoverageIgnore - no need to test this
190
+     */
191
+    public function getMigratedVersions() {
192
+        $this->createMigrationTable();
193
+        $qb = $this->connection->getQueryBuilder();
194
+
195
+        $qb->select('version')
196
+            ->from('migrations')
197
+            ->where($qb->expr()->eq('app', $qb->createNamedParameter($this->getApp())))
198
+            ->orderBy('version');
199
+
200
+        $result = $qb->execute();
201
+        $rows = $result->fetchAll(\PDO::FETCH_COLUMN);
202
+        $result->closeCursor();
203
+
204
+        return $rows;
205
+    }
206
+
207
+    /**
208
+     * Returns all versions which are available in the migration folder
209
+     *
210
+     * @return array
211
+     */
212
+    public function getAvailableVersions() {
213
+        $this->ensureMigrationsAreLoaded();
214
+        return array_map('strval', array_keys($this->migrations));
215
+    }
216
+
217
+    protected function findMigrations() {
218
+        $directory = realpath($this->migrationsPath);
219
+        if ($directory === false || !file_exists($directory) || !is_dir($directory)) {
220
+            return [];
221
+        }
222
+
223
+        $iterator = new \RegexIterator(
224
+            new \RecursiveIteratorIterator(
225
+                new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS),
226
+                \RecursiveIteratorIterator::LEAVES_ONLY
227
+            ),
228
+            '#^.+\\/Version[^\\/]{1,255}\\.php$#i',
229
+            \RegexIterator::GET_MATCH);
230
+
231
+        $files = array_keys(iterator_to_array($iterator));
232
+        uasort($files, function ($a, $b) {
233
+            preg_match('/^Version(\d+)Date(\d+)\\.php$/', basename($a), $matchA);
234
+            preg_match('/^Version(\d+)Date(\d+)\\.php$/', basename($b), $matchB);
235
+            if (!empty($matchA) && !empty($matchB)) {
236
+                if ($matchA[1] !== $matchB[1]) {
237
+                    return ($matchA[1] < $matchB[1]) ? -1 : 1;
238
+                }
239
+                return ($matchA[2] < $matchB[2]) ? -1 : 1;
240
+            }
241
+            return (basename($a) < basename($b)) ? -1 : 1;
242
+        });
243
+
244
+        $migrations = [];
245
+
246
+        foreach ($files as $file) {
247
+            $className = basename($file, '.php');
248
+            $version = (string) substr($className, 7);
249
+            if ($version === '0') {
250
+                throw new \InvalidArgumentException(
251
+                    "Cannot load a migrations with the name '$version' because it is a reserved number"
252
+                );
253
+            }
254
+            $migrations[$version] = sprintf('%s\\%s', $this->migrationsNamespace, $className);
255
+        }
256
+
257
+        return $migrations;
258
+    }
259
+
260
+    /**
261
+     * @param string $to
262
+     * @return string[]
263
+     */
264
+    private function getMigrationsToExecute($to) {
265
+        $knownMigrations = $this->getMigratedVersions();
266
+        $availableMigrations = $this->getAvailableVersions();
267
+
268
+        $toBeExecuted = [];
269
+        foreach ($availableMigrations as $v) {
270
+            if ($to !== 'latest' && $v > $to) {
271
+                continue;
272
+            }
273
+            if ($this->shallBeExecuted($v, $knownMigrations)) {
274
+                $toBeExecuted[] = $v;
275
+            }
276
+        }
277
+
278
+        return $toBeExecuted;
279
+    }
280
+
281
+    /**
282
+     * @param string $m
283
+     * @param string[] $knownMigrations
284
+     * @return bool
285
+     */
286
+    private function shallBeExecuted($m, $knownMigrations) {
287
+        if (in_array($m, $knownMigrations)) {
288
+            return false;
289
+        }
290
+
291
+        return true;
292
+    }
293
+
294
+    /**
295
+     * @param string $version
296
+     */
297
+    private function markAsExecuted($version) {
298
+        $this->connection->insertIfNotExist('*PREFIX*migrations', [
299
+            'app' => $this->appName,
300
+            'version' => $version
301
+        ]);
302
+    }
303
+
304
+    /**
305
+     * Returns the name of the table which holds the already applied versions
306
+     *
307
+     * @return string
308
+     */
309
+    public function getMigrationsTableName() {
310
+        return $this->connection->getPrefix() . 'migrations';
311
+    }
312
+
313
+    /**
314
+     * Returns the namespace of the version classes
315
+     *
316
+     * @return string
317
+     */
318
+    public function getMigrationsNamespace() {
319
+        return $this->migrationsNamespace;
320
+    }
321
+
322
+    /**
323
+     * Returns the directory which holds the versions
324
+     *
325
+     * @return string
326
+     */
327
+    public function getMigrationsDirectory() {
328
+        return $this->migrationsPath;
329
+    }
330
+
331
+    /**
332
+     * Return the explicit version for the aliases; current, next, prev, latest
333
+     *
334
+     * @param string $alias
335
+     * @return mixed|null|string
336
+     */
337
+    public function getMigration($alias) {
338
+        switch ($alias) {
339
+            case 'current':
340
+                return $this->getCurrentVersion();
341
+            case 'next':
342
+                return $this->getRelativeVersion($this->getCurrentVersion(), 1);
343
+            case 'prev':
344
+                return $this->getRelativeVersion($this->getCurrentVersion(), -1);
345
+            case 'latest':
346
+                $this->ensureMigrationsAreLoaded();
347
+
348
+                $migrations = $this->getAvailableVersions();
349
+                return @end($migrations);
350
+        }
351
+        return '0';
352
+    }
353
+
354
+    /**
355
+     * @param string $version
356
+     * @param int $delta
357
+     * @return null|string
358
+     */
359
+    private function getRelativeVersion($version, $delta) {
360
+        $this->ensureMigrationsAreLoaded();
361
+
362
+        $versions = $this->getAvailableVersions();
363
+        array_unshift($versions, 0);
364
+        $offset = array_search($version, $versions, true);
365
+        if ($offset === false || !isset($versions[$offset + $delta])) {
366
+            // Unknown version or delta out of bounds.
367
+            return null;
368
+        }
369
+
370
+        return (string) $versions[$offset + $delta];
371
+    }
372
+
373
+    /**
374
+     * @return string
375
+     */
376
+    private function getCurrentVersion() {
377
+        $m = $this->getMigratedVersions();
378
+        if (count($m) === 0) {
379
+            return '0';
380
+        }
381
+        $migrations = array_values($m);
382
+        return @end($migrations);
383
+    }
384
+
385
+    /**
386
+     * @param string $version
387
+     * @return string
388
+     * @throws \InvalidArgumentException
389
+     */
390
+    private function getClass($version) {
391
+        $this->ensureMigrationsAreLoaded();
392
+
393
+        if (isset($this->migrations[$version])) {
394
+            return $this->migrations[$version];
395
+        }
396
+
397
+        throw new \InvalidArgumentException("Version $version is unknown.");
398
+    }
399
+
400
+    /**
401
+     * Allows to set an IOutput implementation which is used for logging progress and messages
402
+     *
403
+     * @param IOutput $output
404
+     */
405
+    public function setOutput(IOutput $output) {
406
+        $this->output = $output;
407
+    }
408
+
409
+    /**
410
+     * Applies all not yet applied versions up to $to
411
+     *
412
+     * @param string $to
413
+     * @param bool $schemaOnly
414
+     * @throws \InvalidArgumentException
415
+     */
416
+    public function migrate($to = 'latest', $schemaOnly = false) {
417
+        if ($schemaOnly) {
418
+            $this->migrateSchemaOnly($to);
419
+            return;
420
+        }
421
+
422
+        // read known migrations
423
+        $toBeExecuted = $this->getMigrationsToExecute($to);
424
+        foreach ($toBeExecuted as $version) {
425
+            try {
426
+                $this->executeStep($version, $schemaOnly);
427
+            } catch (DriverException $e) {
428
+                // The exception itself does not contain the name of the migration,
429
+                // so we wrap it here, to make debugging easier.
430
+                throw new \Exception('Database error when running migration ' . $to . ' for app ' . $this->getApp(), 0, $e);
431
+            }
432
+        }
433
+    }
434
+
435
+    /**
436
+     * Applies all not yet applied versions up to $to
437
+     *
438
+     * @param string $to
439
+     * @throws \InvalidArgumentException
440
+     */
441
+    public function migrateSchemaOnly($to = 'latest') {
442
+        // read known migrations
443
+        $toBeExecuted = $this->getMigrationsToExecute($to);
444
+
445
+        if (empty($toBeExecuted)) {
446
+            return;
447
+        }
448
+
449
+        $toSchema = null;
450
+        foreach ($toBeExecuted as $version) {
451
+            $instance = $this->createInstance($version);
452
+
453
+            $toSchema = $instance->changeSchema($this->output, function () use ($toSchema) {
454
+                return $toSchema ?: new SchemaWrapper($this->connection);
455
+            }, ['tablePrefix' => $this->connection->getPrefix()]) ?: $toSchema;
456
+        }
457
+
458
+        if ($toSchema instanceof SchemaWrapper) {
459
+            $targetSchema = $toSchema->getWrappedSchema();
460
+            if ($this->checkOracle) {
461
+                $beforeSchema = $this->connection->createSchema();
462
+                $this->ensureOracleConstraints($beforeSchema, $targetSchema, strlen($this->connection->getPrefix()));
463
+            }
464
+            $this->connection->migrateToSchema($targetSchema);
465
+            $toSchema->performDropTableCalls();
466
+        }
467
+
468
+        foreach ($toBeExecuted as $version) {
469
+            $this->markAsExecuted($version);
470
+        }
471
+    }
472
+
473
+    /**
474
+     * Get the human readable descriptions for the migration steps to run
475
+     *
476
+     * @param string $to
477
+     * @return string[] [$name => $description]
478
+     */
479
+    public function describeMigrationStep($to = 'latest') {
480
+        $toBeExecuted = $this->getMigrationsToExecute($to);
481
+        $description = [];
482
+        foreach ($toBeExecuted as $version) {
483
+            $migration = $this->createInstance($version);
484
+            if ($migration->name()) {
485
+                $description[$migration->name()] = $migration->description();
486
+            }
487
+        }
488
+        return $description;
489
+    }
490
+
491
+    /**
492
+     * @param string $version
493
+     * @return IMigrationStep
494
+     * @throws \InvalidArgumentException
495
+     */
496
+    protected function createInstance($version) {
497
+        $class = $this->getClass($version);
498
+        try {
499
+            $s = \OC::$server->query($class);
500
+
501
+            if (!$s instanceof IMigrationStep) {
502
+                throw new \InvalidArgumentException('Not a valid migration');
503
+            }
504
+        } catch (QueryException $e) {
505
+            if (class_exists($class)) {
506
+                $s = new $class();
507
+            } else {
508
+                throw new \InvalidArgumentException("Migration step '$class' is unknown");
509
+            }
510
+        }
511
+
512
+        return $s;
513
+    }
514
+
515
+    /**
516
+     * Executes one explicit version
517
+     *
518
+     * @param string $version
519
+     * @param bool $schemaOnly
520
+     * @throws \InvalidArgumentException
521
+     */
522
+    public function executeStep($version, $schemaOnly = false) {
523
+        $instance = $this->createInstance($version);
524
+
525
+        if (!$schemaOnly) {
526
+            $instance->preSchemaChange($this->output, function () {
527
+                return new SchemaWrapper($this->connection);
528
+            }, ['tablePrefix' => $this->connection->getPrefix()]);
529
+        }
530
+
531
+        $toSchema = $instance->changeSchema($this->output, function () {
532
+            return new SchemaWrapper($this->connection);
533
+        }, ['tablePrefix' => $this->connection->getPrefix()]);
534
+
535
+        if ($toSchema instanceof SchemaWrapper) {
536
+            $targetSchema = $toSchema->getWrappedSchema();
537
+            if ($this->checkOracle) {
538
+                $sourceSchema = $this->connection->createSchema();
539
+                $this->ensureOracleConstraints($sourceSchema, $targetSchema, strlen($this->connection->getPrefix()));
540
+            }
541
+            $this->connection->migrateToSchema($targetSchema);
542
+            $toSchema->performDropTableCalls();
543
+        }
544
+
545
+        if (!$schemaOnly) {
546
+            $instance->postSchemaChange($this->output, function () {
547
+                return new SchemaWrapper($this->connection);
548
+            }, ['tablePrefix' => $this->connection->getPrefix()]);
549
+        }
550
+
551
+        $this->markAsExecuted($version);
552
+    }
553
+
554
+    /**
555
+     * Naming constraints:
556
+     * - Tables names must be 30 chars or shorter (27 + oc_ prefix)
557
+     * - Column names must be 30 chars or shorter
558
+     * - Index names must be 30 chars or shorter
559
+     * - Sequence names must be 30 chars or shorter
560
+     * - Primary key names must be set or the table name 23 chars or shorter
561
+     *
562
+     * Data constraints:
563
+     * - Columns with "NotNull" can not have empty string as default value
564
+     * - Columns with "NotNull" can not have number 0 as default value
565
+     * - Columns with type "bool" (which is in fact integer of length 1) can not be "NotNull" as it can not store 0/false
566
+     *
567
+     * @param Schema $sourceSchema
568
+     * @param Schema $targetSchema
569
+     * @param int $prefixLength
570
+     * @throws \Doctrine\DBAL\Exception
571
+     */
572
+    public function ensureOracleConstraints(Schema $sourceSchema, Schema $targetSchema, int $prefixLength) {
573
+        $sequences = $targetSchema->getSequences();
574
+
575
+        foreach ($targetSchema->getTables() as $table) {
576
+            try {
577
+                $sourceTable = $sourceSchema->getTable($table->getName());
578
+            } catch (SchemaException $e) {
579
+                if (\strlen($table->getName()) - $prefixLength > 27) {
580
+                    throw new \InvalidArgumentException('Table name "' . $table->getName() . '" is too long.');
581
+                }
582
+                $sourceTable = null;
583
+            }
584
+
585
+            foreach ($table->getColumns() as $thing) {
586
+                if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && \strlen($thing->getName()) > 30) {
587
+                    throw new \InvalidArgumentException('Column name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
588
+                }
589
+
590
+                if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && $thing->getNotnull() && $thing->getDefault() === ''
591
+                    && $sourceTable instanceof Table && !$sourceTable->hasColumn($thing->getName())) {
592
+                    throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is NotNull, but has empty string or null as default.');
593
+                }
594
+
595
+                if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && $thing->getNotnull() && $thing->getType()->getName() === Types::BOOLEAN) {
596
+                    throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is type Bool and also NotNull, so it can not store "false".');
597
+                }
598
+            }
599
+
600
+            foreach ($table->getIndexes() as $thing) {
601
+                if ((!$sourceTable instanceof Table || !$sourceTable->hasIndex($thing->getName())) && \strlen($thing->getName()) > 30) {
602
+                    throw new \InvalidArgumentException('Index name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
603
+                }
604
+            }
605
+
606
+            foreach ($table->getForeignKeys() as $thing) {
607
+                if ((!$sourceTable instanceof Table || !$sourceTable->hasForeignKey($thing->getName())) && \strlen($thing->getName()) > 30) {
608
+                    throw new \InvalidArgumentException('Foreign key name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
609
+                }
610
+            }
611
+
612
+            $primaryKey = $table->getPrimaryKey();
613
+            if ($primaryKey instanceof Index && (!$sourceTable instanceof Table || !$sourceTable->hasPrimaryKey())) {
614
+                $indexName = strtolower($primaryKey->getName());
615
+                $isUsingDefaultName = $indexName === 'primary';
616
+
617
+                if ($this->connection->getDatabasePlatform() instanceof PostgreSQL94Platform) {
618
+                    $defaultName = $table->getName() . '_pkey';
619
+                    $isUsingDefaultName = strtolower($defaultName) === $indexName;
620
+
621
+                    if ($isUsingDefaultName) {
622
+                        $sequenceName = $table->getName() . '_' . implode('_', $primaryKey->getColumns()) . '_seq';
623
+                        $sequences = array_filter($sequences, function (Sequence $sequence) use ($sequenceName) {
624
+                            return $sequence->getName() !== $sequenceName;
625
+                        });
626
+                    }
627
+                } elseif ($this->connection->getDatabasePlatform() instanceof OraclePlatform) {
628
+                    $defaultName = $table->getName() . '_seq';
629
+                    $isUsingDefaultName = strtolower($defaultName) === $indexName;
630
+                }
631
+
632
+                if (!$isUsingDefaultName && \strlen($indexName) > 30) {
633
+                    throw new \InvalidArgumentException('Primary index name on "' . $table->getName() . '" is too long.');
634
+                }
635
+                if ($isUsingDefaultName && \strlen($table->getName()) - $prefixLength >= 23) {
636
+                    throw new \InvalidArgumentException('Primary index name on "' . $table->getName() . '" is too long.');
637
+                }
638
+            }
639
+        }
640
+
641
+        foreach ($sequences as $sequence) {
642
+            if (!$sourceSchema->hasSequence($sequence->getName()) && \strlen($sequence->getName()) > 30) {
643
+                throw new \InvalidArgumentException('Sequence name "' . $sequence->getName() . '" is too long.');
644
+            }
645
+        }
646
+    }
647
+
648
+    private function ensureMigrationsAreLoaded() {
649
+        if (empty($this->migrations)) {
650
+            $this->migrations = $this->findMigrations();
651
+        }
652
+    }
653 653
 }
Please login to merge, or discard this patch.
Spacing   +23 added lines, -23 removed lines patch added patch discarded remove patch
@@ -78,7 +78,7 @@  discard block
 block discarded – undo
78 78
 		}
79 79
 
80 80
 		if ($appName === 'core') {
81
-			$this->migrationsPath = \OC::$SERVERROOT . '/core/Migrations';
81
+			$this->migrationsPath = \OC::$SERVERROOT.'/core/Migrations';
82 82
 			$this->migrationsNamespace = 'OC\\Core\\Migrations';
83 83
 			$this->checkOracle = true;
84 84
 		} else {
@@ -88,10 +88,10 @@  discard block
 block discarded – undo
88 88
 			$appPath = $appLocator->getAppPath($appName);
89 89
 			$namespace = App::buildAppNamespace($appName);
90 90
 			$this->migrationsPath = "$appPath/lib/Migration";
91
-			$this->migrationsNamespace = $namespace . '\\Migration';
91
+			$this->migrationsNamespace = $namespace.'\\Migration';
92 92
 
93 93
 			$infoParser = new InfoParser();
94
-			$info = $infoParser->parse($appPath . '/appinfo/info.xml');
94
+			$info = $infoParser->parse($appPath.'/appinfo/info.xml');
95 95
 			if (!isset($info['dependencies']['database'])) {
96 96
 				$this->checkOracle = true;
97 97
 			} else {
@@ -229,7 +229,7 @@  discard block
 block discarded – undo
229 229
 			\RegexIterator::GET_MATCH);
230 230
 
231 231
 		$files = array_keys(iterator_to_array($iterator));
232
-		uasort($files, function ($a, $b) {
232
+		uasort($files, function($a, $b) {
233 233
 			preg_match('/^Version(\d+)Date(\d+)\\.php$/', basename($a), $matchA);
234 234
 			preg_match('/^Version(\d+)Date(\d+)\\.php$/', basename($b), $matchB);
235 235
 			if (!empty($matchA) && !empty($matchB)) {
@@ -307,7 +307,7 @@  discard block
 block discarded – undo
307 307
 	 * @return string
308 308
 	 */
309 309
 	public function getMigrationsTableName() {
310
-		return $this->connection->getPrefix() . 'migrations';
310
+		return $this->connection->getPrefix().'migrations';
311 311
 	}
312 312
 
313 313
 	/**
@@ -427,7 +427,7 @@  discard block
 block discarded – undo
427 427
 			} catch (DriverException $e) {
428 428
 				// The exception itself does not contain the name of the migration,
429 429
 				// so we wrap it here, to make debugging easier.
430
-				throw new \Exception('Database error when running migration ' . $to . ' for app ' . $this->getApp(), 0, $e);
430
+				throw new \Exception('Database error when running migration '.$to.' for app '.$this->getApp(), 0, $e);
431 431
 			}
432 432
 		}
433 433
 	}
@@ -450,7 +450,7 @@  discard block
 block discarded – undo
450 450
 		foreach ($toBeExecuted as $version) {
451 451
 			$instance = $this->createInstance($version);
452 452
 
453
-			$toSchema = $instance->changeSchema($this->output, function () use ($toSchema) {
453
+			$toSchema = $instance->changeSchema($this->output, function() use ($toSchema) {
454 454
 				return $toSchema ?: new SchemaWrapper($this->connection);
455 455
 			}, ['tablePrefix' => $this->connection->getPrefix()]) ?: $toSchema;
456 456
 		}
@@ -523,12 +523,12 @@  discard block
 block discarded – undo
523 523
 		$instance = $this->createInstance($version);
524 524
 
525 525
 		if (!$schemaOnly) {
526
-			$instance->preSchemaChange($this->output, function () {
526
+			$instance->preSchemaChange($this->output, function() {
527 527
 				return new SchemaWrapper($this->connection);
528 528
 			}, ['tablePrefix' => $this->connection->getPrefix()]);
529 529
 		}
530 530
 
531
-		$toSchema = $instance->changeSchema($this->output, function () {
531
+		$toSchema = $instance->changeSchema($this->output, function() {
532 532
 			return new SchemaWrapper($this->connection);
533 533
 		}, ['tablePrefix' => $this->connection->getPrefix()]);
534 534
 
@@ -543,7 +543,7 @@  discard block
 block discarded – undo
543 543
 		}
544 544
 
545 545
 		if (!$schemaOnly) {
546
-			$instance->postSchemaChange($this->output, function () {
546
+			$instance->postSchemaChange($this->output, function() {
547 547
 				return new SchemaWrapper($this->connection);
548 548
 			}, ['tablePrefix' => $this->connection->getPrefix()]);
549 549
 		}
@@ -577,35 +577,35 @@  discard block
 block discarded – undo
577 577
 				$sourceTable = $sourceSchema->getTable($table->getName());
578 578
 			} catch (SchemaException $e) {
579 579
 				if (\strlen($table->getName()) - $prefixLength > 27) {
580
-					throw new \InvalidArgumentException('Table name "' . $table->getName() . '" is too long.');
580
+					throw new \InvalidArgumentException('Table name "'.$table->getName().'" is too long.');
581 581
 				}
582 582
 				$sourceTable = null;
583 583
 			}
584 584
 
585 585
 			foreach ($table->getColumns() as $thing) {
586 586
 				if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && \strlen($thing->getName()) > 30) {
587
-					throw new \InvalidArgumentException('Column name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
587
+					throw new \InvalidArgumentException('Column name "'.$table->getName().'"."'.$thing->getName().'" is too long.');
588 588
 				}
589 589
 
590 590
 				if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && $thing->getNotnull() && $thing->getDefault() === ''
591 591
 					&& $sourceTable instanceof Table && !$sourceTable->hasColumn($thing->getName())) {
592
-					throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is NotNull, but has empty string or null as default.');
592
+					throw new \InvalidArgumentException('Column "'.$table->getName().'"."'.$thing->getName().'" is NotNull, but has empty string or null as default.');
593 593
 				}
594 594
 
595 595
 				if ((!$sourceTable instanceof Table || !$sourceTable->hasColumn($thing->getName())) && $thing->getNotnull() && $thing->getType()->getName() === Types::BOOLEAN) {
596
-					throw new \InvalidArgumentException('Column "' . $table->getName() . '"."' . $thing->getName() . '" is type Bool and also NotNull, so it can not store "false".');
596
+					throw new \InvalidArgumentException('Column "'.$table->getName().'"."'.$thing->getName().'" is type Bool and also NotNull, so it can not store "false".');
597 597
 				}
598 598
 			}
599 599
 
600 600
 			foreach ($table->getIndexes() as $thing) {
601 601
 				if ((!$sourceTable instanceof Table || !$sourceTable->hasIndex($thing->getName())) && \strlen($thing->getName()) > 30) {
602
-					throw new \InvalidArgumentException('Index name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
602
+					throw new \InvalidArgumentException('Index name "'.$table->getName().'"."'.$thing->getName().'" is too long.');
603 603
 				}
604 604
 			}
605 605
 
606 606
 			foreach ($table->getForeignKeys() as $thing) {
607 607
 				if ((!$sourceTable instanceof Table || !$sourceTable->hasForeignKey($thing->getName())) && \strlen($thing->getName()) > 30) {
608
-					throw new \InvalidArgumentException('Foreign key name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
608
+					throw new \InvalidArgumentException('Foreign key name "'.$table->getName().'"."'.$thing->getName().'" is too long.');
609 609
 				}
610 610
 			}
611 611
 
@@ -615,32 +615,32 @@  discard block
 block discarded – undo
615 615
 				$isUsingDefaultName = $indexName === 'primary';
616 616
 
617 617
 				if ($this->connection->getDatabasePlatform() instanceof PostgreSQL94Platform) {
618
-					$defaultName = $table->getName() . '_pkey';
618
+					$defaultName = $table->getName().'_pkey';
619 619
 					$isUsingDefaultName = strtolower($defaultName) === $indexName;
620 620
 
621 621
 					if ($isUsingDefaultName) {
622
-						$sequenceName = $table->getName() . '_' . implode('_', $primaryKey->getColumns()) . '_seq';
623
-						$sequences = array_filter($sequences, function (Sequence $sequence) use ($sequenceName) {
622
+						$sequenceName = $table->getName().'_'.implode('_', $primaryKey->getColumns()).'_seq';
623
+						$sequences = array_filter($sequences, function(Sequence $sequence) use ($sequenceName) {
624 624
 							return $sequence->getName() !== $sequenceName;
625 625
 						});
626 626
 					}
627 627
 				} elseif ($this->connection->getDatabasePlatform() instanceof OraclePlatform) {
628
-					$defaultName = $table->getName() . '_seq';
628
+					$defaultName = $table->getName().'_seq';
629 629
 					$isUsingDefaultName = strtolower($defaultName) === $indexName;
630 630
 				}
631 631
 
632 632
 				if (!$isUsingDefaultName && \strlen($indexName) > 30) {
633
-					throw new \InvalidArgumentException('Primary index name on "' . $table->getName() . '" is too long.');
633
+					throw new \InvalidArgumentException('Primary index name on "'.$table->getName().'" is too long.');
634 634
 				}
635 635
 				if ($isUsingDefaultName && \strlen($table->getName()) - $prefixLength >= 23) {
636
-					throw new \InvalidArgumentException('Primary index name on "' . $table->getName() . '" is too long.');
636
+					throw new \InvalidArgumentException('Primary index name on "'.$table->getName().'" is too long.');
637 637
 				}
638 638
 			}
639 639
 		}
640 640
 
641 641
 		foreach ($sequences as $sequence) {
642 642
 			if (!$sourceSchema->hasSequence($sequence->getName()) && \strlen($sequence->getName()) > 30) {
643
-				throw new \InvalidArgumentException('Sequence name "' . $sequence->getName() . '" is too long.');
643
+				throw new \InvalidArgumentException('Sequence name "'.$sequence->getName().'" is too long.');
644 644
 			}
645 645
 		}
646 646
 	}
Please login to merge, or discard this patch.