This commit is contained in:
Marek
2026-03-24 00:04:55 +01:00
commit c5229e48ed
4225 changed files with 511461 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Doctrine\Bundle\MigrationsBundle\Collector;
use Doctrine\DBAL\Exception;
use Doctrine\Migrations\DependencyFactory;
use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\VarDumper\Cloner\Data;
use Throwable;
use function count;
use function get_class;
/** @final */
class MigrationsCollector extends DataCollector
{
/** @var DependencyFactory */
private $dependencyFactory;
/** @var MigrationsFlattener */
private $flattener;
public function __construct(DependencyFactory $dependencyFactory, MigrationsFlattener $migrationsFlattener)
{
$this->dependencyFactory = $dependencyFactory;
$this->flattener = $migrationsFlattener;
}
/** @return void */
public function collect(Request $request, Response $response, ?Throwable $exception = null)
{
if ($this->data !== []) {
return;
}
$metadataStorage = $this->dependencyFactory->getMetadataStorage();
$planCalculator = $this->dependencyFactory->getMigrationPlanCalculator();
try {
$executedMigrations = $metadataStorage->getExecutedMigrations();
} catch (Exception $dbalException) {
$this->dependencyFactory->getLogger()->error(
'error while trying to collect executed migrations',
['exception' => $dbalException]
);
return;
}
$availableMigrations = $planCalculator->getMigrations();
$this->data['available_migrations_count'] = count($availableMigrations);
$unavailableMigrations = $executedMigrations->unavailableSubset($availableMigrations);
$this->data['unavailable_migrations_count'] = count($unavailableMigrations);
$newMigrations = $availableMigrations->newSubset($executedMigrations);
$this->data['new_migrations'] = $this->flattener->flattenAvailableMigrations($newMigrations);
$this->data['executed_migrations'] = $this->flattener->flattenExecutedMigrations($executedMigrations, $availableMigrations);
$this->data['storage'] = get_class($metadataStorage);
$configuration = $this->dependencyFactory->getConfiguration();
$storage = $configuration->getMetadataStorageConfiguration();
if ($storage instanceof TableMetadataStorageConfiguration) {
$this->data['table'] = $storage->getTableName();
$this->data['column'] = $storage->getVersionColumnName();
}
$connection = $this->dependencyFactory->getConnection();
$this->data['driver'] = get_class($connection->getDriver());
$this->data['name'] = $connection->getDatabase();
$this->data['namespaces'] = $configuration->getMigrationDirectories();
}
/** @return string */
public function getName()
{
return 'doctrine_migrations';
}
/** @return array<string, mixed>|Data */
public function getData()
{
return $this->data;
}
/** @return void */
public function reset()
{
$this->data = [];
}
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Doctrine\Bundle\MigrationsBundle\Collector;
use DateTimeImmutable;
use Doctrine\Migrations\Metadata\AvailableMigration;
use Doctrine\Migrations\Metadata\AvailableMigrationsList;
use Doctrine\Migrations\Metadata\ExecutedMigration;
use Doctrine\Migrations\Metadata\ExecutedMigrationsList;
use ReflectionClass;
use function array_map;
/** @final */
class MigrationsFlattener
{
/**
* @return array{
* version: string,
* is_new: true,
* is_unavailable: bool,
* description: string,
* executed_at: null,
* execution_time: null,
* file: string|false,
* }[]
*/
public function flattenAvailableMigrations(AvailableMigrationsList $migrationsList): array
{
return array_map(static function (AvailableMigration $migration) {
return [
'version' => (string) $migration->getVersion(),
'is_new' => true,
'is_unavailable' => false,
'description' => $migration->getMigration()->getDescription(),
'executed_at' => null,
'execution_time' => null,
'file' => (new ReflectionClass($migration->getMigration()))->getFileName(),
];
}, $migrationsList->getItems());
}
/**
* @return array{
* version: string,
* is_new: false,
* is_unavailable: bool,
* description: string|null,
* executed_at: DateTimeImmutable|null,
* execution_time: float|null,
* file: string|false|null,
* }[]
*/
public function flattenExecutedMigrations(ExecutedMigrationsList $migrationsList, AvailableMigrationsList $availableMigrations): array
{
return array_map(static function (ExecutedMigration $migration) use ($availableMigrations) {
$availableMigration = $availableMigrations->hasMigration($migration->getVersion())
? $availableMigrations->getMigration($migration->getVersion())->getMigration()
: null;
return [
'version' => (string) $migration->getVersion(),
'is_new' => false,
'is_unavailable' => $availableMigration === null,
'description' => $availableMigration !== null ? $availableMigration->getDescription() : null,
'executed_at' => $migration->getExecutedAt(),
'execution_time' => $migration->getExecutionTime(),
'file' => $availableMigration !== null ? (new ReflectionClass($availableMigration))->getFileName() : null,
];
}, $migrationsList->getItems());
}
}

View File

@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
namespace Doctrine\Bundle\MigrationsBundle\DependencyInjection\CompilerPass;
use Doctrine\Migrations\DependencyFactory;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use function array_keys;
use function assert;
use function count;
use function implode;
use function is_array;
use function is_string;
use function sprintf;
/** @final */
class ConfigureDependencyFactoryPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (! $container->has('doctrine')) {
throw new RuntimeException('DoctrineMigrationsBundle requires DoctrineBundle to be enabled.');
}
$diDefinition = $container->getDefinition('doctrine.migrations.dependency_factory');
$preferredConnection = $container->getParameter('doctrine.migrations.preferred_connection');
assert(is_string($preferredConnection) || $preferredConnection === null);
// explicitly use configured connection
if ($preferredConnection !== null) {
$this->validatePreferredConnection($container, $preferredConnection);
$loaderDefinition = $container->getDefinition('doctrine.migrations.connection_registry_loader');
$loaderDefinition->setArgument(1, $preferredConnection);
$diDefinition->setFactory([DependencyFactory::class, 'fromConnection']);
$diDefinition->setArgument(1, new Reference('doctrine.migrations.connection_registry_loader'));
return;
}
$preferredEm = $container->getParameter('doctrine.migrations.preferred_em');
assert(is_string($preferredEm) || $preferredEm === null);
// explicitly use configured entity manager
if ($preferredEm !== null) {
$this->validatePreferredEm($container, $preferredEm);
$loaderDefinition = $container->getDefinition('doctrine.migrations.entity_manager_registry_loader');
$loaderDefinition->setArgument(1, $preferredEm);
$diDefinition->setFactory([DependencyFactory::class, 'fromEntityManager']);
$diDefinition->setArgument(1, new Reference('doctrine.migrations.entity_manager_registry_loader'));
return;
}
// try to use any/default entity manager
if (
$container->hasParameter('doctrine.entity_managers')
&& is_array($container->getParameter('doctrine.entity_managers'))
&& count($container->getParameter('doctrine.entity_managers')) > 0
) {
$diDefinition->setFactory([DependencyFactory::class, 'fromEntityManager']);
$diDefinition->setArgument(1, new Reference('doctrine.migrations.entity_manager_registry_loader'));
return;
}
// fallback on any/default connection
$diDefinition->setFactory([DependencyFactory::class, 'fromConnection']);
$diDefinition->setArgument(1, new Reference('doctrine.migrations.connection_registry_loader'));
}
private function validatePreferredConnection(ContainerBuilder $container, string $preferredConnection): void
{
/** @var array<string, string> $allowedConnections */
$allowedConnections = $container->getParameter('doctrine.connections');
if (! isset($allowedConnections[$preferredConnection])) {
throw new InvalidArgumentException(sprintf(
'The "%s" connection is not defined. Did you mean one of the following: %s',
$preferredConnection,
implode(', ', array_keys($allowedConnections))
));
}
}
private function validatePreferredEm(ContainerBuilder $container, string $preferredEm): void
{
if (
! $container->hasParameter('doctrine.entity_managers')
|| ! is_array($container->getParameter('doctrine.entity_managers'))
|| count($container->getParameter('doctrine.entity_managers')) === 0
) {
throw new InvalidArgumentException(sprintf(
'The "%s" entity manager is not defined. It seems that you do not have configured any entity manager in the DoctrineBundle.',
$preferredEm
));
}
/** @var array<string, string> $allowedEms */
$allowedEms = $container->getParameter('doctrine.entity_managers');
if (! isset($allowedEms[$preferredEm])) {
throw new InvalidArgumentException(sprintf(
'The "%s" entity manager is not defined. Did you mean one of the following: %s',
$preferredEm,
implode(', ', array_keys($allowedEms))
));
}
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Doctrine\Bundle\MigrationsBundle\DependencyInjection\CompilerPass;
use Doctrine\DBAL\Connection;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
/** @internal */
final class RegisterMigrationsPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (! $container->hasDefinition('doctrine.migrations.service_migrations_repository')) {
return;
}
$migrationRefs = [];
foreach ($container->findTaggedServiceIds('doctrine_migrations.migration', true) as $id => $attributes) {
$definition = $container->getDefinition($id);
$definition->setBindings([
Connection::class => new BoundArgument(new Reference('doctrine.migrations.connection'), false),
LoggerInterface::class => new BoundArgument(new Reference('doctrine.migrations.logger'), false),
]);
$migrationRefs[$id] = new TypedReference($id, $definition->getClass());
}
$container->getDefinition('doctrine.migrations.service_migrations_repository')
->replaceArgument(0, new ServiceLocatorArgument($migrationRefs));
}
}

View File

@@ -0,0 +1,178 @@
<?php
declare(strict_types=1);
namespace Doctrine\Bundle\MigrationsBundle\DependencyInjection;
use ReflectionClass;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use function array_filter;
use function array_keys;
use function constant;
use function count;
use function in_array;
use function is_string;
use function strlen;
use function strpos;
use function strtoupper;
use function substr;
/** @final */
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('doctrine_migrations');
$rootNode = $treeBuilder->getRootNode();
$organizeMigrationModes = $this->getOrganizeMigrationsModes();
$rootNode
->fixXmlConfig('migration', 'migrations')
->fixXmlConfig('migrations_path', 'migrations_paths')
->children()
->booleanNode('enable_service_migrations')
->info('Whether to enable fetching migrations from the service container.')
->defaultFalse()
->end()
->arrayNode('migrations_paths')
->info('A list of namespace/path pairs where to look for migrations.')
->defaultValue([])
->useAttributeAsKey('namespace')
->prototype('scalar')->end()
->end()
->arrayNode('services')
->info('A set of services to pass to the underlying doctrine/migrations library, allowing to change its behaviour.')
->useAttributeAsKey('service')
->defaultValue([])
->validate()
->ifTrue(static function (array $v): bool {
return count(array_filter(array_keys($v), static function (string $doctrineService): bool {
return strpos($doctrineService, 'Doctrine\Migrations\\') !== 0;
})) !== 0;
})
->thenInvalid('Valid services for the DoctrineMigrationsBundle must be in the "Doctrine\Migrations" namespace.')
->end()
->prototype('scalar')->end()
->end()
->arrayNode('factories')
->info('A set of callables to pass to the underlying doctrine/migrations library as services, allowing to change its behaviour.')
->useAttributeAsKey('factory')
->defaultValue([])
->validate()
->ifTrue(static function (array $v): bool {
return count(array_filter(array_keys($v), static function (string $doctrineService): bool {
return strpos($doctrineService, 'Doctrine\Migrations\\') !== 0;
})) !== 0;
})
->thenInvalid('Valid callables for the DoctrineMigrationsBundle must be in the "Doctrine\Migrations" namespace.')
->end()
->prototype('scalar')->end()
->end()
->arrayNode('storage')
->addDefaultsIfNotSet()
->info('Storage to use for migration status metadata.')
->children()
->arrayNode('table_storage')
->addDefaultsIfNotSet()
->info('The default metadata storage, implemented as a table in the database.')
->children()
->scalarNode('table_name')->defaultValue(null)->cannotBeEmpty()->end()
->scalarNode('version_column_name')->defaultValue(null)->end()
->scalarNode('version_column_length')->defaultValue(null)->end()
->scalarNode('executed_at_column_name')->defaultValue(null)->end()
->scalarNode('execution_time_column_name')->defaultValue(null)->end()
->end()
->end()
->end()
->end()
->arrayNode('migrations')
->info('A list of migrations to load in addition to the one discovered via "migrations_paths".')
->prototype('scalar')->end()
->defaultValue([])
->end()
->scalarNode('connection')
->info('Connection name to use for the migrations database.')
->defaultValue(null)
->end()
->scalarNode('em')
->info('Entity manager name to use for the migrations database (available when doctrine/orm is installed).')
->defaultValue(null)
->end()
->scalarNode('all_or_nothing')
->info('Run all migrations in a transaction.')
->defaultValue(false)
->end()
->scalarNode('check_database_platform')
->info('Adds an extra check in the generated migrations to allow execution only on the same platform as they were initially generated on.')
->defaultValue(true)
->end()
->scalarNode('custom_template')
->info('Custom template path for generated migration classes.')
->defaultValue(null)
->end()
->scalarNode('organize_migrations')
->defaultValue(false)
->info('Organize migrations mode. Possible values are: "BY_YEAR", "BY_YEAR_AND_MONTH", false')
->validate()
->ifTrue(static function ($v) use ($organizeMigrationModes): bool {
if ($v === false) {
return false;
}
return ! is_string($v) || ! in_array(strtoupper($v), $organizeMigrationModes, true);
})
->thenInvalid('Invalid organize migrations mode value %s')
->end()
->validate()
->ifString()
->then(static function ($v) {
return constant('Doctrine\Migrations\Configuration\Configuration::VERSIONS_ORGANIZATION_' . strtoupper($v));
})
->end()
->end()
->booleanNode('enable_profiler')
->info('Whether or not to enable the profiler collector to calculate and visualize migration status. This adds some queries overhead.')
->defaultFalse()
->end()
->booleanNode('transactional')
->info('Whether or not to wrap migrations in a single transaction.')
->defaultTrue()
->end()
->end();
return $treeBuilder;
}
/**
* Find organize migrations modes for their names
*
* @return string[]
*/
private function getOrganizeMigrationsModes(): array
{
$constPrefix = 'VERSIONS_ORGANIZATION_';
$prefixLen = strlen($constPrefix);
$refClass = new ReflectionClass('Doctrine\Migrations\Configuration\Configuration');
$constsArray = array_keys($refClass->getConstants());
$namesArray = [];
foreach ($constsArray as $constant) {
if (strpos($constant, $constPrefix) !== 0) {
continue;
}
$namesArray[] = substr($constant, $prefixLen);
}
return $namesArray;
}
}

View File

@@ -0,0 +1,227 @@
<?php
declare(strict_types=1);
namespace Doctrine\Bundle\MigrationsBundle\DependencyInjection;
use Doctrine\Bundle\MigrationsBundle\Collector\MigrationsCollector;
use Doctrine\Bundle\MigrationsBundle\Collector\MigrationsFlattener;
use Doctrine\Migrations\AbstractMigration;
use Doctrine\Migrations\Metadata\Storage\MetadataStorage;
use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration;
use Doctrine\Migrations\MigrationsRepository;
use Doctrine\Migrations\Version\MigrationFactory;
use InvalidArgumentException;
use RuntimeException;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use function array_keys;
use function assert;
use function explode;
use function implode;
use function interface_exists;
use function is_array;
use function sprintf;
use function strlen;
use function substr;
/** @final */
class DoctrineMigrationsExtension extends Extension
{
/**
* Responds to the migrations configuration parameter.
*
* {@inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$locator = new FileLocator(__DIR__ . '/../../config/');
$loader = new PhpFileLoader($container, $locator);
$loader->load('services.php');
if ($config['enable_service_migrations']) {
$container->registerForAutoconfiguration(AbstractMigration::class)
->addTag('doctrine_migrations.migration');
if (! isset($config['services'][MigrationsRepository::class])) {
$config['services'][MigrationsRepository::class] = 'doctrine.migrations.service_migrations_repository';
}
} else {
$container->removeDefinition('doctrine.migrations.service_migrations_repository');
$container->removeDefinition('doctrine.migrations.connection');
$container->removeDefinition('doctrine.migrations.logger');
}
$configurationDefinition = $container->getDefinition('doctrine.migrations.configuration');
foreach ($config['migrations_paths'] as $ns => $path) {
$path = $this->checkIfBundleRelativePath($path, $container);
$configurationDefinition->addMethodCall('addMigrationsDirectory', [$ns, $path]);
}
foreach ($config['migrations'] as $migrationClass) {
$configurationDefinition->addMethodCall('addMigrationClass', [$migrationClass]);
}
if ($config['organize_migrations'] !== false) {
$configurationDefinition->addMethodCall('setMigrationOrganization', [$config['organize_migrations']]);
}
if ($config['custom_template'] !== null) {
$configurationDefinition->addMethodCall('setCustomTemplate', [$config['custom_template']]);
}
$configurationDefinition->addMethodCall('setAllOrNothing', [$config['all_or_nothing']]);
$configurationDefinition->addMethodCall('setCheckDatabasePlatform', [$config['check_database_platform']]);
if ($config['enable_profiler']) {
$this->registerCollector($container);
}
$configurationDefinition->addMethodCall('setTransactional', [$config['transactional']]);
$diDefinition = $container->getDefinition('doctrine.migrations.dependency_factory');
if (! isset($config['services'][MigrationFactory::class])) {
$config['services'][MigrationFactory::class] = 'doctrine.migrations.migrations_factory';
}
foreach ($config['services'] as $doctrineId => $symfonyId) {
$diDefinition->addMethodCall('setDefinition', [$doctrineId, new ServiceClosureArgument(new Reference($symfonyId))]);
}
foreach ($config['factories'] as $doctrineId => $symfonyId) {
$diDefinition->addMethodCall('setDefinition', [$doctrineId, new Reference($symfonyId)]);
}
if (isset($config['services'][MetadataStorage::class])) {
$container->removeDefinition('doctrine_migrations.schema_filter_listener');
} else {
$filterDefinition = $container->getDefinition('doctrine_migrations.schema_filter_listener');
$storageConfiguration = $config['storage']['table_storage'];
$storageDefinition = new Definition(TableMetadataStorageConfiguration::class);
$container->setDefinition('doctrine.migrations.storage.table_storage', $storageDefinition);
$container->setAlias('doctrine.migrations.metadata_storage', 'doctrine.migrations.storage.table_storage');
if ($storageConfiguration['table_name'] === null) {
$filterDefinition->addArgument('doctrine_migration_versions');
} else {
$storageDefinition->addMethodCall('setTableName', [$storageConfiguration['table_name']]);
$filterDefinition->addArgument($storageConfiguration['table_name']);
}
if ($storageConfiguration['version_column_name'] !== null) {
$storageDefinition->addMethodCall('setVersionColumnName', [$storageConfiguration['version_column_name']]);
}
if ($storageConfiguration['version_column_length'] !== null) {
$storageDefinition->addMethodCall('setVersionColumnLength', [$storageConfiguration['version_column_length']]);
}
if ($storageConfiguration['executed_at_column_name'] !== null) {
$storageDefinition->addMethodCall('setExecutedAtColumnName', [$storageConfiguration['executed_at_column_name']]);
}
if ($storageConfiguration['execution_time_column_name'] !== null) {
$storageDefinition->addMethodCall('setExecutionTimeColumnName', [$storageConfiguration['execution_time_column_name']]);
}
$configurationDefinition->addMethodCall('setMetadataStorageConfiguration', [new Reference('doctrine.migrations.storage.table_storage')]);
// Add tag to the filter for each Doctrine connection, so the table is ignored for multiple connections
if ($container->hasParameter('doctrine.connections')) {
/** @var array<string, string> $connections */
$connections = $container->getParameter('doctrine.connections');
foreach (array_keys($connections) as $connection) {
$filterDefinition->addTag('doctrine.dbal.schema_filter', ['connection' => $connection]);
}
}
}
if ($config['em'] !== null && $config['connection'] !== null) {
throw new InvalidArgumentException(
'You cannot specify both "connection" and "em" in the DoctrineMigrationsBundle configurations.'
);
}
$container->setParameter('doctrine.migrations.preferred_em', $config['em']);
$container->setParameter('doctrine.migrations.preferred_connection', $config['connection']);
if (interface_exists(ContainerAwareInterface::class)) {
return;
}
$container->removeDefinition('doctrine.migrations.container_aware_migrations_factory');
}
private function checkIfBundleRelativePath(string $path, ContainerBuilder $container): string
{
if (isset($path[0]) && $path[0] === '@') {
$pathParts = explode('/', $path);
$bundleName = substr($pathParts[0], 1);
$bundlePath = $this->getBundlePath($bundleName, $container);
return $bundlePath . substr($path, strlen('@' . $bundleName));
}
return $path;
}
private function getBundlePath(string $bundleName, ContainerBuilder $container): string
{
$bundleMetadata = $container->getParameter('kernel.bundles_metadata');
assert(is_array($bundleMetadata));
if (! isset($bundleMetadata[$bundleName])) {
throw new RuntimeException(sprintf(
'The bundle "%s" has not been registered, available bundles: %s',
$bundleName,
implode(', ', array_keys($bundleMetadata))
));
}
return $bundleMetadata[$bundleName]['path'];
}
private function registerCollector(ContainerBuilder $container): void
{
$flattenerDefinition = new Definition(MigrationsFlattener::class);
$container->setDefinition('doctrine_migrations.migrations_flattener', $flattenerDefinition);
$collectorDefinition = new Definition(MigrationsCollector::class, [
new Reference('doctrine.migrations.dependency_factory'),
new Reference('doctrine_migrations.migrations_flattener'),
]);
$collectorDefinition
->addTag('data_collector', [
'template' => '@DoctrineMigrations/Collector/migrations.html.twig',
'id' => 'doctrine_migrations',
'priority' => '249',
]);
$container->setDefinition('doctrine_migrations.migrations_collector', $collectorDefinition);
}
public function getXsdValidationBasePath(): string
{
return __DIR__ . '/../../config/schema';
}
public function getNamespace(): string
{
return 'http://symfony.com/schema/dic/doctrine/migrations/3.0';
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Doctrine\Bundle\MigrationsBundle;
use Doctrine\Bundle\MigrationsBundle\DependencyInjection\CompilerPass\ConfigureDependencyFactoryPass;
use Doctrine\Bundle\MigrationsBundle\DependencyInjection\CompilerPass\RegisterMigrationsPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use function dirname;
/** @final */
class DoctrineMigrationsBundle extends Bundle
{
public function build(ContainerBuilder $container): void
{
$container->addCompilerPass(new ConfigureDependencyFactoryPass());
$container->addCompilerPass(new RegisterMigrationsPass());
}
public function getPath(): string
{
return dirname(__DIR__);
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Doctrine\Bundle\MigrationsBundle\EventListener;
use Doctrine\DBAL\Schema\AbstractAsset;
use Doctrine\DBAL\Schema\Name\OptionallyQualifiedName;
use Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand;
use Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
/**
* Acts as a schema filter that hides the migration metadata table except
* when the execution context is that of command inside the migrations
* namespace.
*/
final class SchemaFilterListener
{
/** @var string */
private $configurationTableName;
public function __construct(string $configurationTableName)
{
$this->configurationTableName = $configurationTableName;
}
/** @var bool */
private $enabled = false;
/** @param AbstractAsset<OptionallyQualifiedName>|string $asset */
public function __invoke($asset): bool
{
if (! $this->enabled) {
return true;
}
if ($asset instanceof AbstractAsset) {
$asset = $asset->getName();
}
return $asset !== $this->configurationTableName;
}
public function onConsoleCommand(ConsoleCommandEvent $event): void
{
$command = $event->getCommand();
if (! $command instanceof ValidateSchemaCommand && ! $command instanceof UpdateCommand) {
return;
}
$this->enabled = true;
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Doctrine\Bundle\MigrationsBundle\MigrationsFactory;
use Doctrine\Migrations\AbstractMigration;
use Doctrine\Migrations\Version\MigrationFactory;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use function trigger_deprecation;
/** @deprecated This class is not compatible with Symfony >= 7 */
class ContainerAwareMigrationFactory implements MigrationFactory
{
/** @var ContainerInterface */
private $container;
/** @var MigrationFactory */
private $migrationFactory;
public function __construct(MigrationFactory $migrationFactory, ContainerInterface $container)
{
$this->container = $container;
$this->migrationFactory = $migrationFactory;
}
public function createVersion(string $migrationClassName): AbstractMigration
{
$migration = $this->migrationFactory->createVersion($migrationClassName);
if ($migration instanceof ContainerAwareInterface) {
trigger_deprecation('doctrine/doctrine-migrations-bundle', '3.3', 'Migration "%s" implements "%s" to gain access to the application\'s service container. This method is deprecated and won\'t work with Symfony 7.', $migrationClassName, ContainerAwareInterface::class);
$migration->setContainer($this->container);
}
return $migration;
}
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Doctrine\Bundle\MigrationsBundle\MigrationsRepository;
use Doctrine\Migrations\AbstractMigration;
use Doctrine\Migrations\Exception\MigrationClassNotFound;
use Doctrine\Migrations\Metadata\AvailableMigration;
use Doctrine\Migrations\Metadata\AvailableMigrationsSet;
use Doctrine\Migrations\MigrationsRepository;
use Doctrine\Migrations\Version\Version;
use Symfony\Contracts\Service\ServiceProviderInterface;
/** @internal */
final class ServiceMigrationsRepository implements MigrationsRepository
{
/** @var ServiceProviderInterface<AbstractMigration> */
private $container;
/** @var array<string, AvailableMigration> */
private $migrations = [];
/** @param ServiceProviderInterface<AbstractMigration> $container */
public function __construct(ServiceProviderInterface $container)
{
$this->container = $container;
}
public function hasMigration(string $version): bool
{
return isset($this->migrations[$version]) || $this->container->has($version);
}
public function getMigration(Version $version): AvailableMigration
{
$this->loadMigrationFromContainer($version);
return $this->migrations[(string) $version];
}
/**
* Returns a non-sorted set of migrations.
*/
public function getMigrations(): AvailableMigrationsSet
{
foreach ($this->container->getProvidedServices() as $id) {
$this->loadMigrationFromContainer(new Version($id));
}
return new AvailableMigrationsSet($this->migrations);
}
private function loadMigrationFromContainer(Version $version): void
{
$id = (string) $version;
if (isset($this->migrations[$id])) {
return;
}
if (! $this->container->has($id)) {
throw MigrationClassNotFound::new($id);
}
$this->migrations[$id] = new AvailableMigration($version, $this->container->get($id));
}
}