197 lines
5.9 KiB
PHP
197 lines
5.9 KiB
PHP
|
<?php
|
||
|
|
||
|
declare(strict_types=1);
|
||
|
|
||
|
namespace Doctrine\Migrations;
|
||
|
|
||
|
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||
|
use Doctrine\DBAL\Schema\AbstractSchemaManager;
|
||
|
use Doctrine\DBAL\Schema\Table;
|
||
|
use Doctrine\Migrations\Exception\NoTablesFound;
|
||
|
use Doctrine\Migrations\Generator\Generator;
|
||
|
use Doctrine\Migrations\Generator\SqlGenerator;
|
||
|
use InvalidArgumentException;
|
||
|
|
||
|
use function array_merge;
|
||
|
use function count;
|
||
|
use function implode;
|
||
|
use function preg_last_error;
|
||
|
use function preg_match;
|
||
|
use function restore_error_handler;
|
||
|
use function set_error_handler;
|
||
|
use function sprintf;
|
||
|
|
||
|
use const PREG_BACKTRACK_LIMIT_ERROR;
|
||
|
use const PREG_BAD_UTF8_ERROR;
|
||
|
use const PREG_BAD_UTF8_OFFSET_ERROR;
|
||
|
use const PREG_INTERNAL_ERROR;
|
||
|
use const PREG_RECURSION_LIMIT_ERROR;
|
||
|
|
||
|
/**
|
||
|
* The SchemaDumper class is responsible for dumping the current state of your database schema to a migration. This
|
||
|
* is to be used in conjunction with the Rollup class.
|
||
|
*
|
||
|
* @internal
|
||
|
*
|
||
|
* @see Doctrine\Migrations\Rollup
|
||
|
*/
|
||
|
class SchemaDumper
|
||
|
{
|
||
|
private AbstractPlatform $platform;
|
||
|
|
||
|
/** @var AbstractSchemaManager<AbstractPlatform> */
|
||
|
private AbstractSchemaManager $schemaManager;
|
||
|
|
||
|
private Generator $migrationGenerator;
|
||
|
|
||
|
private SqlGenerator $migrationSqlGenerator;
|
||
|
|
||
|
/** @var string[] */
|
||
|
private array $excludedTablesRegexes;
|
||
|
|
||
|
/**
|
||
|
* @param AbstractSchemaManager<AbstractPlatform> $schemaManager
|
||
|
* @param string[] $excludedTablesRegexes
|
||
|
*/
|
||
|
public function __construct(
|
||
|
AbstractPlatform $platform,
|
||
|
AbstractSchemaManager $schemaManager,
|
||
|
Generator $migrationGenerator,
|
||
|
SqlGenerator $migrationSqlGenerator,
|
||
|
array $excludedTablesRegexes = []
|
||
|
) {
|
||
|
$this->platform = $platform;
|
||
|
$this->schemaManager = $schemaManager;
|
||
|
$this->migrationGenerator = $migrationGenerator;
|
||
|
$this->migrationSqlGenerator = $migrationSqlGenerator;
|
||
|
$this->excludedTablesRegexes = $excludedTablesRegexes;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string[] $excludedTablesRegexes
|
||
|
*
|
||
|
* @throws NoTablesFound
|
||
|
*/
|
||
|
public function dump(
|
||
|
string $fqcn,
|
||
|
array $excludedTablesRegexes = [],
|
||
|
bool $formatted = false,
|
||
|
int $lineLength = 120
|
||
|
): string {
|
||
|
$schema = $this->schemaManager->introspectSchema();
|
||
|
|
||
|
$up = [];
|
||
|
$down = [];
|
||
|
|
||
|
foreach ($schema->getTables() as $table) {
|
||
|
if ($this->shouldSkipTable($table, $excludedTablesRegexes)) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$upSql = $this->platform->getCreateTableSQL($table);
|
||
|
|
||
|
$upCode = $this->migrationSqlGenerator->generate(
|
||
|
$upSql,
|
||
|
$formatted,
|
||
|
$lineLength
|
||
|
);
|
||
|
|
||
|
if ($upCode !== '') {
|
||
|
$up[] = $upCode;
|
||
|
}
|
||
|
|
||
|
$downSql = [$this->platform->getDropTableSQL($table)];
|
||
|
|
||
|
$downCode = $this->migrationSqlGenerator->generate(
|
||
|
$downSql,
|
||
|
$formatted,
|
||
|
$lineLength
|
||
|
);
|
||
|
|
||
|
if ($downCode === '') {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$down[] = $downCode;
|
||
|
}
|
||
|
|
||
|
if (count($up) === 0) {
|
||
|
throw NoTablesFound::new();
|
||
|
}
|
||
|
|
||
|
$up = implode("\n", $up);
|
||
|
$down = implode("\n", $down);
|
||
|
|
||
|
return $this->migrationGenerator->generateMigration(
|
||
|
$fqcn,
|
||
|
$up,
|
||
|
$down
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string[] $excludedTablesRegexes
|
||
|
*/
|
||
|
private function shouldSkipTable(Table $table, array $excludedTablesRegexes): bool
|
||
|
{
|
||
|
foreach (array_merge($excludedTablesRegexes, $this->excludedTablesRegexes) as $regex) {
|
||
|
if (self::pregMatch($regex, $table->getName()) !== 0) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A local wrapper for "preg_match" which will throw a InvalidArgumentException if there
|
||
|
* is an internal error in the PCRE engine.
|
||
|
* Copied from https://github.com/symfony/symfony/blob/62216ea67762b18982ca3db73c391b0748a49d49/src/Symfony/Component/Yaml/Parser.php#L1072-L1090
|
||
|
*
|
||
|
* @internal
|
||
|
*
|
||
|
* @param mixed[] $matches
|
||
|
* @param int-mask-of<PREG_OFFSET_CAPTURE|PREG_UNMATCHED_AS_NULL> $flags
|
||
|
*/
|
||
|
private static function pregMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
|
||
|
{
|
||
|
try {
|
||
|
$errorMessages = [];
|
||
|
set_error_handler(static function (int $severity, string $message, string $file, int $line) use (&$errorMessages): bool {
|
||
|
$errorMessages[] = $message;
|
||
|
|
||
|
return true;
|
||
|
});
|
||
|
$ret = preg_match($pattern, $subject, $matches, $flags, $offset);
|
||
|
} finally {
|
||
|
restore_error_handler();
|
||
|
}
|
||
|
|
||
|
if ($ret === false) {
|
||
|
switch (preg_last_error()) {
|
||
|
case PREG_INTERNAL_ERROR:
|
||
|
$error = sprintf('Internal PCRE error, please check your Regex. Reported errors: %s.', implode(', ', $errorMessages));
|
||
|
break;
|
||
|
case PREG_BACKTRACK_LIMIT_ERROR:
|
||
|
$error = 'pcre.backtrack_limit reached.';
|
||
|
break;
|
||
|
case PREG_RECURSION_LIMIT_ERROR:
|
||
|
$error = 'pcre.recursion_limit reached.';
|
||
|
break;
|
||
|
case PREG_BAD_UTF8_ERROR:
|
||
|
$error = 'Malformed UTF-8 data.';
|
||
|
break;
|
||
|
case PREG_BAD_UTF8_OFFSET_ERROR:
|
||
|
$error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
|
||
|
break;
|
||
|
default:
|
||
|
$error = 'Error.';
|
||
|
}
|
||
|
|
||
|
throw new InvalidArgumentException($error);
|
||
|
}
|
||
|
|
||
|
return $ret;
|
||
|
}
|
||
|
}
|