The start of something beautiful

This commit is contained in:
2024-09-11 22:48:07 -06:00
parent 45acea47f3
commit f5997ee5ec
5614 changed files with 630696 additions and 0 deletions
@@ -0,0 +1,4 @@
branches: ["2.2.x", "3.0.x", "3.1.x", "3.2.x", "3.3.x", "3.4.x"]
maintained_branches: ["2.2.x", "3.3.x", "3.4.x"]
doc_dir: {"2.2.x": "Resources/doc/", "3.3.x": "Resources/doc/", "3.4.x": "docs/"}
dev_branch: "3.4.x"
@@ -0,0 +1,95 @@
<?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;
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 (! empty($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 = [];
}
}
@@ -0,0 +1,73 @@
<?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;
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,
'description' => $availableMigration ? $availableMigration->getDescription() : null,
'executed_at' => $migration->getExecutedAt(),
'execution_time' => $migration->getExecutionTime(),
'file' => $availableMigration ? (new ReflectionClass($availableMigration))->getFileName() : null,
];
}, $migrationsList->getItems());
}
}
@@ -0,0 +1,115 @@
<?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;
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))
));
}
}
}
@@ -0,0 +1,180 @@
<?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;
/**
* DoctrineMigrationsExtension configuration structure.
*/
class Configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree.
*
* @return TreeBuilder The config tree builder
*/
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()
->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;
}
}
@@ -0,0 +1,201 @@
<?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\Metadata\Storage\MetadataStorage;
use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration;
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\XmlFileLoader;
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;
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__ . '/../Resources/config/');
$loader = new XmlFileLoader($container, $locator);
$loader->load('services.xml');
$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])) {
$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) {
$storageDefinition->addMethodCall('setTableName', [$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')]);
}
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);
}
/**
* Returns the base path for the XSD files.
*
* @return string The XSD base path
*/
public function getXsdValidationBasePath(): string
{
return __DIR__ . '/../Resources/config/schema';
}
public function getNamespace(): string
{
return 'http://symfony.com/schema/dic/doctrine/migrations/3.0';
}
}
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Bundle\MigrationsBundle;
use Doctrine\Bundle\MigrationsBundle\DependencyInjection\CompilerPass\ConfigureDependencyFactoryPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* Bundle.
*/
class DoctrineMigrationsBundle extends Bundle
{
/** @return void */
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new ConfigureDependencyFactoryPass());
}
}
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2006-2013 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -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;
}
}
@@ -0,0 +1,9 @@
DoctrineMigrationsBundle
========================
This bundle integrates the [Doctrine Migrations library](http://www.doctrine-project.org/projects/migrations.html)
into Symfony applications. Database migrations help you version the changes in
your database schema and apply them in a predictable way on every server running
the application.
[Read the documentation of this bundle](https://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html).
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://symfony.com/schema/dic/doctrine/migrations/3.0"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://symfony.com/schema/dic/doctrine/migrations/3.0"
elementFormDefault="qualified"
>
<xsd:element name="config">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="migrations-path" maxOccurs="unbounded">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="namespace" type="xsd:string"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="services" maxOccurs="unbounded" minOccurs="0">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="service" type="xsd:string"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="migration" type="xsd:string" maxOccurs="unbounded" minOccurs="0"/>
<xsd:element name="storage" minOccurs="0">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="table-storage" minOccurs="0">
<xsd:complexType>
<xsd:attribute name="table-name" type="xsd:string"/>
<xsd:attribute name="version-column-name" type="xsd:string"/>
<xsd:attribute name="version-column-length" type="xsd:positiveInteger"/>
<xsd:attribute name="executed-at-column-name" type="xsd:string"/>
<xsd:attribute name="execution-time-column-name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string"/>
<xsd:attribute name="em" type="xsd:string"/>
<xsd:attribute name="connection" type="xsd:string"/>
<xsd:attribute name="sorter" type="xsd:string"/>
<xsd:attribute name="all_or_nothing" type="xsd:boolean"/>
<xsd:attribute name="check_database_platform" type="xsd:boolean"/>
<xsd:attribute name="custom_template" type="xsd:string"/>
<xsd:attribute name="organize-migrations">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="BY_YEAR"/>
<xsd:enumeration value="BY_YEAR_AND_MONTH"/>
<xsd:enumeration value="false"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>
@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="doctrine.migrations.dependency_factory" class="Doctrine\Migrations\DependencyFactory" public="false">
<factory></factory>
<argument type="service" id="doctrine.migrations.configuration_loader"/>
<argument></argument>
<argument type="service" id="logger" on-invalid="null"></argument>
</service>
<service id="doctrine.migrations.configuration_loader" class="Doctrine\Migrations\Configuration\Migration\ExistingConfiguration" public="false">
<argument type="service" id="doctrine.migrations.configuration"/>
</service>
<service id="doctrine.migrations.connection_loader" class="Doctrine\Migrations\Configuration\Connection\ExistingConnection" public="false">
</service>
<service id="doctrine.migrations.em_loader" class="Doctrine\Migrations\Configuration\EntityManager\ExistingEntityManager" public="false">
</service>
<service id="doctrine.migrations.entity_manager_registry_loader" class="Doctrine\Migrations\Configuration\EntityManager\ManagerRegistryEntityManager" public="false">
<factory class="Doctrine\Migrations\Configuration\EntityManager\ManagerRegistryEntityManager" method="withSimpleDefault"/>
<argument type="service" id="doctrine"/>
</service>
<service id="doctrine.migrations.connection_registry_loader" class="Doctrine\Migrations\Configuration\Connection\ConnectionRegistryConnection" public="false">
<factory class="Doctrine\Migrations\Configuration\Connection\ConnectionRegistryConnection" method="withSimpleDefault"/>
<argument type="service" id="doctrine"/>
</service>
<service id="doctrine.migrations.configuration" class="Doctrine\Migrations\Configuration\Configuration" public="false">
</service>
<service id="doctrine.migrations.migrations_factory" class="Doctrine\Migrations\Version\MigrationFactory">
<factory service="doctrine.migrations.dependency_factory" method="getMigrationFactory"/>
</service>
<service id="doctrine.migrations.container_aware_migrations_factory"
class="Doctrine\Bundle\MigrationsBundle\MigrationsFactory\ContainerAwareMigrationFactory"
decorates="doctrine.migrations.migrations_factory"
>
<argument id="doctrine.migrations.container_aware_migrations_factory.inner" type="service"/>
<argument id="service_container" type="service"/>
</service>
<service id="doctrine_migrations.diff_command" class="Doctrine\Migrations\Tools\Console\Command\DiffCommand">
<argument type="service" id="doctrine.migrations.dependency_factory"/>
<argument>doctrine:migrations:diff</argument>
<tag name="console.command" command="doctrine:migrations:diff" />
</service>
<service id="doctrine_migrations.sync_metadata_command" class="Doctrine\Migrations\Tools\Console\Command\SyncMetadataCommand">
<argument type="service" id="doctrine.migrations.dependency_factory"/>
<argument>doctrine:migrations:sync-metadata-storage</argument>
<tag name="console.command" command="doctrine:migrations:sync-metadata-storage" />
</service>
<service id="doctrine_migrations.versions_command" class="Doctrine\Migrations\Tools\Console\Command\ListCommand">
<argument type="service" id="doctrine.migrations.dependency_factory"/>
<argument>doctrine:migrations:versions</argument>
<tag name="console.command" command="doctrine:migrations:list" />
</service>
<service id="doctrine_migrations.current_command" class="Doctrine\Migrations\Tools\Console\Command\CurrentCommand">
<argument type="service" id="doctrine.migrations.dependency_factory"/>
<argument>doctrine:migrations:current</argument>
<tag name="console.command" command="doctrine:migrations:current"/>
</service>
<service id="doctrine_migrations.dump_schema_command" class="Doctrine\Migrations\Tools\Console\Command\DumpSchemaCommand">
<argument type="service" id="doctrine.migrations.dependency_factory"/>
<argument>doctrine:migrations:dump-schema</argument>
<tag name="console.command" command="doctrine:migrations:dump-schema"/>
</service>
<service id="doctrine_migrations.execute_command" class="Doctrine\Migrations\Tools\Console\Command\ExecuteCommand">
<argument type="service" id="doctrine.migrations.dependency_factory"/>
<argument>doctrine:migrations:execute</argument>
<tag name="console.command" command="doctrine:migrations:execute"/>
</service>
<service id="doctrine_migrations.generate_command" class="Doctrine\Migrations\Tools\Console\Command\GenerateCommand">
<argument type="service" id="doctrine.migrations.dependency_factory"/>
<argument>doctrine:migrations:generate</argument>
<tag name="console.command" command="doctrine:migrations:generate"/>
</service>
<service id="doctrine_migrations.latest_command" class="Doctrine\Migrations\Tools\Console\Command\LatestCommand">
<argument type="service" id="doctrine.migrations.dependency_factory"/>
<argument>doctrine:migrations:latest</argument>
<tag name="console.command" command="doctrine:migrations:latest"/>
</service>
<service id="doctrine_migrations.migrate_command" class="Doctrine\Migrations\Tools\Console\Command\MigrateCommand">
<argument type="service" id="doctrine.migrations.dependency_factory"/>
<argument>doctrine:migrations:migrate</argument>
<tag name="console.command" command="doctrine:migrations:migrate" />
</service>
<service id="doctrine_migrations.rollup_command" class="Doctrine\Migrations\Tools\Console\Command\RollupCommand">
<argument type="service" id="doctrine.migrations.dependency_factory"/>
<argument>doctrine:migrations:rollup</argument>
<tag name="console.command" command="doctrine:migrations:rollup" />
</service>
<service id="doctrine_migrations.status_command" class="Doctrine\Migrations\Tools\Console\Command\StatusCommand">
<argument type="service" id="doctrine.migrations.dependency_factory"/>
<argument>doctrine:migrations:status</argument>
<tag name="console.command" command="doctrine:migrations:status" />
</service>
<service id="doctrine_migrations.up_to_date_command" class="Doctrine\Migrations\Tools\Console\Command\UpToDateCommand">
<argument type="service" id="doctrine.migrations.dependency_factory"/>
<argument>doctrine:migrations:up-to-date</argument>
<tag name="console.command" command="doctrine:migrations:up-to-date" />
</service>
<service id="doctrine_migrations.version_command" class="Doctrine\Migrations\Tools\Console\Command\VersionCommand">
<argument type="service" id="doctrine.migrations.dependency_factory"/>
<argument>doctrine:migrations:version</argument>
<tag name="console.command" command="doctrine:migrations:version" />
</service>
</services>
</container>
@@ -0,0 +1,467 @@
DoctrineMigrationsBundle
========================
Database migrations are a way to safely update your database schema both locally
and on production. Instead of running the ``doctrine:schema:update`` command or
applying the database changes manually with SQL statements, migrations allow to
replicate the changes in your database schema in a safe manner.
Migrations are available in Symfony applications via the `DoctrineMigrationsBundle`_,
which uses the external `Doctrine Database Migrations`_ library. Read the
`documentation`_ of that library if you need a general introduction about migrations.
Installation
------------
Run this command in your terminal:
.. code-block:: terminal
$ composer require doctrine/doctrine-migrations-bundle "^3.0"
If you don't use `Symfony Flex`_, you must enable the bundle manually in the application:
.. code-block:: php
// config/bundles.php
// in older Symfony apps, enable the bundle in app/AppKernel.php
return [
// ...
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
];
Configuration
-------------
If you use Symfony Flex, the ``doctrine_migrations.yaml`` config file is created
automatically. Otherwise, create the following file and configure it for your
application:
.. code-block:: yaml
# config/packages/doctrine_migrations.yaml
doctrine_migrations:
# List of namespace/path pairs to search for migrations, at least one required
migrations_paths:
'App\Migrations': '%kernel.project_dir%/src/App'
'AnotherApp\Migrations': '/path/to/other/migrations'
'SomeBundle\Migrations': '@SomeBundle/Migrations'
# List of additional migration classes to be loaded, optional
migrations:
- 'App\Migrations\Version123'
- 'App\Migrations\Version123'
# Connection to use for the migrations
connection: default
# Entity manager to use for migrations. This overrides the "connection" setting.
em: default
storage:
# Default (SQL table) metadata storage configuration
table_storage:
table_name: 'doctrine_migration_versions'
version_column_name: 'version'
version_column_length: 192
executed_at_column_name: 'executed_at'
# Possible values: "BY_YEAR", "BY_YEAR_AND_MONTH", false
organize_migrations: false
# Path to your custom migrations template
custom_template: ~
# Run all migrations in a transaction.
all_or_nothing: false
# Adds an extra check in the generated migrations to ensure that is executed on the same database type.
check_database_platform: true
# Whether or not to wrap migrations in a single transaction.
transactional: true
# Whether or not to enable the profiler collector to calculate and visualize migration status. This adds some queries overhead.
# enable_profiler: false
services:
# Custom migration sorting service id
'Doctrine\Migrations\Version\Comparator': ~
# Custom migration classes factory
'Doctrine\Migrations\Version\MigrationFactory': ~
factories:
# Custom migration sorting service id via callables (MyCallableFactory must be a callable)
'Doctrine\Migrations\Version\Comparator': 'MyCallableFactory'
- The ``services`` node allows you to provide custom services to the underlying ``DependencyFactory`` part of ``doctrine/migrations``.
- The node ``factories`` is similar to ``services``, with the difference that it accepts only callables.
The provided callable must return the service to be passed to the ``DependencyFactory``.
The callable will receive as first argument the ``DependencyFactory`` itself,
allowing you to fetch other dependencies from the factory while instantiating your custom dependencies.
Usage
-----
All of the migrations functionality is contained in a few console commands:
.. code-block:: terminal
doctrine
doctrine:migrations:current [current] Outputs the current version.
doctrine:migrations:diff [diff] Generate a migration by comparing your current database to your mapping information.
doctrine:migrations:dump-schema [dump-schema] Dump the schema for your database to a migration.
doctrine:migrations:execute [execute] Execute a single migration version up or down manually.
doctrine:migrations:generate [generate] Generate a blank migration class.
doctrine:migrations:latest [latest] Outputs the latest version number
doctrine:migrations:migrate [migrate] Execute a migration to a specified version or the latest available version.
doctrine:migrations:rollup [rollup] Roll migrations up by deleting all tracked versions and inserting the one version that exists.
doctrine:migrations:status [status] View the status of a set of migrations.
doctrine:migrations:up-to-date [up-to-date] Tells you if your schema is up-to-date.
doctrine:migrations:version [version] Manually add and delete migration versions from the version table.
doctrine:migrations:sync-metadata-storage [sync-metadata-storage] Ensures that the metadata storage is at the latest version.
doctrine:migrations:list [list-migrations] Display a list of all available migrations and their status.
Start by getting the status of migrations in your application by running
the ``status`` command:
.. code-block:: terminal
$ php bin/console doctrine:migrations:status
This command will show you generic information about the migration status, such as how many migrations have been
already executed, which still need to run, and the database in use.
Now, you can start working with migrations by generating a new blank migration
class. Later, you'll learn how Doctrine can generate migrations automatically
for you.
.. code-block:: terminal
$ php bin/console doctrine:migrations:generate
Have a look at the newly generated migration class and you will see something
like the following:
.. code-block:: php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20180605025653 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
}
}
If you run the ``status`` command again it will now show that you have one new
migration to execute:
.. code-block:: terminal
$ php bin/console doctrine:migrations:status
Now you can add some migration code to the ``up()`` and ``down()`` methods and
finally migrate when you're ready:
.. code-block:: terminal
$ php bin/console doctrine:migrations:migrate 'DoctrineMigrations\Version20180605025653'
For more information on how to write the migrations themselves (i.e. how to
fill in the ``up()`` and ``down()`` methods), see the official Doctrine Migrations
`documentation`_.
Running Migrations during Deployment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Of course, the end goal of writing migrations is to be able to use them to
reliably update your database structure when you deploy your application.
By running the migrations locally (or on a beta server), you can ensure that
the migrations work as you expect.
When you do finally deploy your application, you just need to remember to run
the ``doctrine:migrations:migrate`` command. Internally, Doctrine creates
a ``migration_versions`` table inside your database and tracks which migrations
have been executed there. So, no matter how many migrations you've created
and executed locally, when you run the command during deployment, Doctrine
will know exactly which migrations it hasn't run yet by looking at the ``migration_versions``
table of your production database. Regardless of what server you're on, you
can always safely run this command to execute only the migrations that haven't
been run yet on *that* particular database.
Skipping Migrations
~~~~~~~~~~~~~~~~~~~
You can skip single migrations by explicitly adding them to the ``migration_versions`` table:
.. code-block:: terminal
$ php bin/console doctrine:migrations:version 'App\Migrations\Version123' --add
.. tip::
Pay attention to the single quotes (``'``) used in the command above, without them
or with the double quotes (``"``) the command will not work properly.
Doctrine will then assume that this migration has already been run and will ignore it.
Migration Dependencies
----------------------
Migrations can have dependencies on external services (such as geolocation, mailer, data processing services...) that
can be used to have more powerful migrations. Those dependencies are not automatically injected into your migrations
but need to be injected using custom migrations factories.
Here is an example on how to inject the service container into your migrations:
.. configuration-block::
.. code-block:: yaml
# config/packages/doctrine_migrations.yaml
doctrine_migrations:
services:
'Doctrine\Migrations\Version\MigrationFactory': 'App\Migrations\Factory\MigrationFactoryDecorator'
# config/services.yaml
services:
App\Migrations\Factory\MigrationFactoryDecorator:
decorates: 'doctrine.migrations.migrations_factory'
arguments: ['@.inner', '@service_container']
.. code-block:: php
declare(strict_types=1);
namespace App\Migrations\Factory;
use Doctrine\Migrations\AbstractMigration;
use Doctrine\Migrations\Version\MigrationFactory;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class MigrationFactoryDecorator implements MigrationFactory
{
private $migrationFactory;
private $container;
public function __construct(MigrationFactory $migrationFactory, ContainerInterface $container)
{
$this->migrationFactory = $migrationFactory;
$this->container = $container;
}
public function createVersion(string $migrationClassName): AbstractMigration
{
$instance = $this->migrationFactory->createVersion($migrationClassName);
if ($instance instanceof ContainerAwareInterface) {
$instance->setContainer($this->container);
}
return $instance;
}
}
.. tip::
If your migration class implements the interface ``Symfony\Component\DependencyInjection\ContainerAwareInterface``
this bundle will automatically inject the default symfony container into your migration class
(this because the ``MigrationFactoryDecorator`` shown in this example is the default migration factory used by this bundle).
Generating Migrations Automatically
-----------------------------------
In reality, you should rarely need to write migrations manually, as the migrations
library can generate migration classes automatically by comparing your Doctrine
mapping information (i.e. what your database *should* look like) with your
actual current database structure.
For example, suppose you create a new ``User`` entity and add mapping information
for Doctrine's ORM:
.. configuration-block::
.. code-block:: php-annotations
// src/Entity/User.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="hello_user")
*/
class User
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $name;
.. code-block:: yaml
# config/doctrine/User.orm.yaml
App\Entity\User:
type: entity
table: user
id:
id:
type: integer
generator:
strategy: AUTO
fields:
name:
type: string
length: 255
.. code-block:: xml
<!-- config/doctrine/User.orm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="App\Entity\User" table="user">
<id name="id" type="integer" column="id">
<generator strategy="AUTO"/>
</id>
<field name="name" column="name" type="string" length="255" />
</entity>
</doctrine-mapping>
With this information, Doctrine is now ready to help you persist your new
``User`` object to and from the ``user`` table. Of course, this table
doesn't exist yet! Generate a new migration for this table automatically by
running the following command:
.. code-block:: terminal
$ php bin/console doctrine:migrations:diff
You should see a message that a new migration class was generated based on
the schema differences. If you open this file, you'll find that it has the
SQL code needed to create the ``user`` table. Next, run the migration
to add the table to your database:
.. code-block:: terminal
$ php bin/console doctrine:migrations:migrate
The moral of the story is this: after each change you make to your Doctrine
mapping information, run the ``doctrine:migrations:diff`` command to automatically
generate your migration classes.
If you do this from the very beginning of your project (i.e. so that even
the first tables were loaded via a migration class), you'll always be able
to create a fresh database and run your migrations in order to get your database
schema fully up to date. In fact, this is an easy and dependable workflow
for your project.
If you don't want to use this workflow and instead create your schema via
``doctrine:schema:create``, you can tell Doctrine to skip all existing migrations:
.. code-block:: terminal
$ php bin/console doctrine:migrations:version --add --all
Otherwise Doctrine will try to run all migrations, which probably will not work.
Manual Tables
-------------
It is a common use case, that in addition to your generated database structure
based on your doctrine entities you might need custom tables. By default such
tables will be removed by the ``doctrine:migrations:diff`` command.
If you follow a specific scheme you can configure doctrine/dbal to ignore those
tables. Let's say all custom tables will be prefixed by ``t_``. In this case you
just have to add the following configuration option to your doctrine configuration:
.. configuration-block::
.. code-block:: yaml
doctrine:
dbal:
schema_filter: ~^(?!t_)~
.. code-block:: xml
<doctrine:dbal schema-filter="~^(?!t_)~" />
.. code-block:: php
$container->loadFromExtension('doctrine', array(
'dbal' => array(
'schema_filter' => '~^(?!t_)~',
// ...
),
// ...
));
This ignores the tables, and any named objects such as sequences, on the DBAL level and they will be ignored by the diff command.
Note that if you have multiple connections configured then the ``schema_filter`` configuration
will need to be placed per-connection.
Troubleshooting out of sync metadata storage issue
--------------------------------------------------
``doctrine/migrations`` relies on a properly configured Database server version in the connection string to manage the table storing the
migrations, also known as the metadata storage.
If you encounter the error ``The metadata storage is not up to date, please run the sync-metadata-storage command to fix this issue.``
when running the command ``doctrine:migrations:migrate`` or the suggested command itself ``doctrine:migrations:sync-metadata-storage`` please
check the database connection string, and make sure that the proper server version is defined. If you are running a MariaDB database,
you should prefix the server version with ``mariadb-`` (ex: ``mariadb-10.2.12``). See the `configuring_database`_ section.
Example connection string for MariaDB:
.. code-block:: terminal
DATABASE_URL=mysql://root:@127.0.0.1:3306/testtest?serverVersion=mariadb-10.4.11
.. _documentation: https://www.doctrine-project.org/projects/doctrine-migrations/en/current/index.html
.. _configuring_database: https://symfony.com/doc/current/doctrine.html#configuring-the-database
.. _DoctrineMigrationsBundle: https://github.com/doctrine/DoctrineMigrationsBundle
.. _`Doctrine Database Migrations`: https://github.com/doctrine/migrations
.. _`Symfony Flex`: https://symfony.com/doc/current/setup/flex.html
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-versions" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<rect x="10" y="5" width="10" height="14" rx="2"></rect>
<line x1="7" y1="7" x2="7" y2="17"></line>
<line x1="4" y1="8" x2="4" y2="16"></line>
</svg>

After

Width:  |  Height:  |  Size: 446 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<polygon fill="#AAA" points="0 0 24 0 24 7 17.5 7 16.5 3 15.5 7 11 7 12 3 3 3 4 7 0 7" />
<polygon fill="#AAA" points="0 8.5 4.5 8.5 6 15.5 0 15.5" />
<polygon fill="#AAA" points="10.5 8.5 15 8.5 13.5 15.5 9 15.5" />
<polygon fill="#AAA" points="18 8.5 24 8.5 24 15.5 19.5 15.5" />
<polygon fill="#AAA" points="0 17 6.5 17 7.5 21 8.5 17 13 17 12 21 21 21 20 17 24 17 24 24 0 24" />
</svg>

After

Width:  |  Height:  |  Size: 493 B

@@ -0,0 +1,252 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block toolbar %}
{% if collector.data.unavailable_migrations_count is defined %}
{% set unavailable_migrations = collector.data.unavailable_migrations_count %}
{% set new_migrations = collector.data.new_migrations|length %}
{% if unavailable_migrations > 0 or new_migrations > 0 %}
{% set executed_migrations = collector.data.executed_migrations|length %}
{% set available_migrations = collector.data.available_migrations_count %}
{% set status_color = unavailable_migrations > 0 ? 'yellow' : '' %}
{% set status_color = new_migrations > 0 ? 'red' : status_color %}
{% set icon %}
{% if profiler_markup_version < 3 %}
{{ include('@DoctrineMigrations/Collector/icon.svg') }}
{% else %}
{{ include('@DoctrineMigrations/Collector/icon-v3.svg') }}
{% endif %}
<span class="sf-toolbar-value">{{ new_migrations + unavailable_migrations }}</span>
{% endset %}
{% set text %}
<div class="sf-toolbar-info-group">
<div class="sf-toolbar-info-piece">
<b>Current Migration</b>
<span>{{ executed_migrations > 0 ? collector.data.executed_migrations|last.version|split('\\')|last : 'n/a' }}</span>
</div>
</div>
<div class="sf-toolbar-info-group">
<div class="sf-toolbar-info-piece">
<span class="sf-toolbar-header">
<b>Database Migrations</b>
</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Executed</b>
<span class="sf-toolbar-status">{{ executed_migrations }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Unavailable</b>
<span class="sf-toolbar-status {{ unavailable_migrations > 0 ? 'sf-toolbar-status-yellow' }}">{{ unavailable_migrations }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Available</b>
<span class="sf-toolbar-status">{{ available_migrations }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>New</b>
<span class="sf-toolbar-status {{ new_migrations > 0 ? 'sf-toolbar-status-red' }}">{{ new_migrations }}</span>
</div>
</div>
{% endset %}
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status_color }) }}
{% endif %}
{% endif %}
{% endblock %}
{% block menu %}
{% if collector.data.unavailable_migrations_count is defined %}
{% set unavailable_migrations = collector.data.unavailable_migrations_count %}
{% set new_migrations = collector.data.new_migrations|length %}
{% set label = unavailable_migrations > 0 ? 'label-status-warning' : '' %}
{% set label = new_migrations > 0 ? 'label-status-error' : label %}
<span class="label {{ label }}">
<span class="icon">
{% if profiler_markup_version < 3 %}
{{ include('@DoctrineMigrations/Collector/icon.svg') }}
{% else %}
{{ include('@DoctrineMigrations/Collector/icon-v3.svg') }}
{% endif %}
</span>
<strong>Migrations</strong>
{% if unavailable_migrations > 0 or new_migrations > 0 %}
<span class="count">
<span>{{ new_migrations + unavailable_migrations }}</span>
</span>
{% endif %}
</span>
{% endif %}
{% endblock %}
{% block panel %}
{% set num_executed_migrations = collector.data.executed_migrations|length %}
{% set num_unavailable_migrations = collector.data.unavailable_migrations_count %}
{% set num_available_migrations = collector.data.available_migrations_count %}
{% set num_new_migrations = collector.data.new_migrations|length %}
<h2>Doctrine Migrations</h2>
<div class="metrics">
<div class="metric">
<span class="value">{{ num_executed_migrations }}</span>
<span class="label">Executed</span>
</div>
{% if profiler_markup_version >= 3 %}
<div class="metric-group">
{% endif %}
<div class="metric">
<span class="value">{{ num_unavailable_migrations }}</span>
<span class="label">Unavailable</span>
</div>
<div class="metric">
<span class="value">{{ num_available_migrations }}</span>
<span class="label">Available</span>
</div>
<div class="metric">
<span class="value">{{ num_new_migrations }}</span>
<span class="label">New</span>
</div>
{% if profiler_markup_version >= 3 %}
</div> {# closes the <div class="metric-group"> #}
{% endif %}
</div>
<div class="sf-tabs">
<div class="tab">
<h3 class="tab-title">
Migrations
<span class="badge {{ num_new_migrations > 0 ? 'status-error' : num_unavailable_migrations > 0 ? 'status-warning' }}">
{{ num_new_migrations > 0 ? num_new_migrations : num_unavailable_migrations > 0 ? num_unavailable_migrations : num_executed_migrations }}
</span>
</h3>
<div class="tab-content">
{{ _self.render_migration_details(collector, profiler_markup_version) }}
</div>
</div>
<div class="tab">
<h3 class="tab-title">Configuration</h3>
<div class="tab-content">
{{ _self.render_configuration_details(collector, profiler_markup_version) }}
</div>
</div>
</div>
{% endblock %}
{% macro render_migration_details(collector) %}
<table>
<thead>
<tr>
<th class="colored font-normal">Version</th>
<th class="colored font-normal">Description</th>
<th class="colored font-normal">Status</th>
<th class="colored font-normal">Executed at</th>
<th class="colored font-normal text-right">Execution time</th>
</tr>
</thead>
{% for migration in collector.data.new_migrations %}
{{ _self.render_migration(migration) }}
{% endfor %}
{% for migration in collector.data.executed_migrations|reverse %}
{{ _self.render_migration(migration) }}
{% endfor %}
</table>
{% endmacro %}
{% macro render_configuration_details(collector) %}
<table>
<thead>
<tr>
<th colspan="2" class="colored font-normal">Storage</th>
</tr>
</thead>
<tr>
<td class="font-normal">Type</td>
<td class="font-normal">{{ collector.data.storage }}</td>
</tr>
{% if collector.data.table is defined %}
<tr>
<td class="font-normal">Table Name</td>
<td class="font-normal">{{ collector.data.table }}</td>
</tr>
{% endif %}
{% if collector.data.column is defined %}
<tr>
<td class="font-normal">Column Name</td>
<td class="font-normal">{{ collector.data.column }}</td>
</tr>
{% endif %}
</table>
<table>
<thead>
<tr>
<th colspan="2" class="colored font-normal">Database</th>
</tr>
</thead>
<tr>
<td class="font-normal">Driver</td>
<td class="font-normal">{{ collector.data.driver }}</td>
</tr>
<tr>
<td class="font-normal">Name</td>
<td class="font-normal">{{ collector.data.name }}</td>
</tr>
</table>
<table>
<thead>
<tr>
<th colspan="2" class="colored font-normal">Migration Namespaces</th>
</tr>
</thead>
{% for namespace, directory in collector.data.namespaces %}
<tr>
<td class="font-normal">{{ namespace }}</td>
<td class="font-normal">{{ directory }}</td>
</tr>
{% endfor %}
</table>
{% endmacro %}
{% macro render_migration(migration, profiler_markup_version) %}
<tr>
<td class="font-normal">
{% if migration.file %}
<a href="{{ migration.file|file_link(1) }}" title="{{ migration.file }}">{{ migration.version }}</a>
{% else %}
{{ migration.version }}
{% endif %}
</td>
<td class="font-normal">{{ migration.description }}</td>
<td class="font-normal align-right">
{% if migration.is_new %}
<span class="{{ profiler_markup_version >= 3 ? 'badge badge-error' : 'label status-error' }}">NOT EXECUTED</span>
{% elseif migration.is_unavailable %}
<span class="{{ profiler_markup_version >= 3 ? 'badge badge-warning' : 'label status-warning' }}">UNAVAILABLE</span>
{% else %}
<span class="{{ profiler_markup_version >= 3 ? 'badge badge-success' : 'label status-success' }}">EXECUTED</span>
{% endif %}
</td>
<td class="font-normal">{{ migration.executed_at ? migration.executed_at|date('M j, Y H:i') : 'n/a' }}</td>
<td class="font-normal text-right">
{% if migration.execution_time is null %}
n/a
{% elseif migration.execution_time < 1 %}
{{ (migration.execution_time * 1000)|number_format(0) }} ms
{% else %}
{{ migration.execution_time|number_format(2) }} seconds
{% endif %}
</td>
</tr>
{% endmacro %}
+86
View File
@@ -0,0 +1,86 @@
# Upgrade
## From 2.x to 3.0.0
- The configuration for the migration namespace and directory changed as follows:
Before
```yaml
doctrine_migrations:
dir_name: '%kernel.project_dir%/src/Migrations'
namespace: DoctrineMigrations
```
After
```yaml
doctrine_migrations:
migrations_paths:
'DoctrineMigrations': '%kernel.project_dir%/src/Migrations'
```
- The configuration for the metadata table definition changed as follows:
Before
```yaml
doctrine_migrations:
table_name: 'migration_versions'
column_name: 'version'
column_length: 14
executed_at_column_name: 'executed_at'
```
After
```yaml
doctrine_migrations:
storage:
table_storage:
table_name: 'migration_versions'
version_column_name: 'version'
version_column_length: 191
executed_at_column_name: 'executed_at'
```
If your project did not originally specify its own table definition configuration, you will need to configure the table name after the upgrade:
```yaml
doctrine_migrations:
storage:
table_storage:
table_name: 'migration_versions'
```
and then run the `doctrine:migrations:sync-metadata-storage` command.
- The migration name has been dropped:
Before
```yaml
doctrine_migrations:
name: 'Application Migrations'
```
After
The parameter `name` has been dropped.
- The default for `table_name` changed from `migration_versions` to `doctrine_migration_versions`. If you did not
specify the `table_name` option, you now need to declare it explicitly to not lose migration data.
```yaml
doctrine_migrations:
storage:
table_storage:
table_name: 'migration_versions'
```
### Underlying doctrine/migrations library
Upgrading this bundle to `3.0` will also update the `doctrine/migrations` library to the version `3.0`.
Backward incompatible changes in `doctrine/migrations` 3.0
are documented in the dedicated [UPGRADE](https://github.com/doctrine/migrations/blob/3.0.x/UPGRADE.md) document.
- The container is not automatically injected anymore when a migration implements `ContainerAwareInterface`. Custom
migration factories should be used to inject additional dependencies into migrations.
@@ -0,0 +1,60 @@
{
"name": "doctrine/doctrine-migrations-bundle",
"type": "symfony-bundle",
"description": "Symfony DoctrineMigrationsBundle",
"keywords": ["DBAL", "Migrations", "Schema"],
"homepage": "https://www.doctrine-project.org",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Doctrine Project",
"homepage": "https://www.doctrine-project.org"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": "^7.2|^8.0",
"symfony/deprecation-contracts": "^2.1 || ^3",
"symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0",
"doctrine/doctrine-bundle": "^2.4",
"doctrine/migrations": "^3.2"
},
"require-dev": {
"composer/semver": "^3.0",
"phpunit/phpunit": "^8.5|^9.5",
"doctrine/coding-standard": "^12",
"phpstan/phpstan": "^1.4",
"phpstan/phpstan-deprecation-rules": "^1",
"phpstan/phpstan-phpunit": "^1",
"phpstan/phpstan-strict-rules": "^1.1",
"phpstan/phpstan-symfony": "^1.3",
"doctrine/orm": "^2.6 || ^3",
"doctrine/persistence": "^2.0 || ^3 ",
"psalm/plugin-phpunit": "^0.18.4",
"psalm/plugin-symfony": "^3 || ^5",
"symfony/phpunit-bridge": "^6.3 || ^7",
"symfony/var-exporter": "^5.4 || ^6 || ^7",
"vimeo/psalm": "^4.30 || ^5.15"
},
"autoload": {
"psr-4": { "Doctrine\\Bundle\\MigrationsBundle\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"autoload-dev": {
"psr-4": { "Doctrine\\Bundle\\MigrationsBundle\\Tests\\": "Tests" }
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}
@@ -0,0 +1,26 @@
parameters:
ignoreErrors:
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1
path: Collector/MigrationsCollector.php
-
message: "#^Only booleans are allowed in a negated boolean, Doctrine\\\\Migrations\\\\AbstractMigration\\|null given\\.$#"
count: 1
path: Collector/MigrationsFlattener.php
-
message: "#^Only booleans are allowed in a ternary operator condition, Doctrine\\\\Migrations\\\\AbstractMigration\\|null given\\.$#"
count: 2
path: Collector/MigrationsFlattener.php
-
message: "#^Call to method setContainer\\(\\) on an unknown class Symfony\\\\Component\\\\DependencyInjection\\\\ContainerAwareInterface\\.$#"
count: 1
path: MigrationsFactory/ContainerAwareMigrationFactory.php
-
message: "#^Class Symfony\\\\Component\\\\DependencyInjection\\\\ContainerAwareInterface not found\\.$#"
count: 2
path: MigrationsFactory/ContainerAwareMigrationFactory.php