diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 5261c295..2952e516 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -1,61 +1,56 @@ doctrine: dbal: - url: '%env(resolve:DATABASE_URL)%' - - # Required for DAMA doctrine test bundle - use_savepoints: true - - # IMPORTANT: You MUST configure your server version, - # either here or in the DATABASE_URL env var (see .env file) - + # 1. GLOBAL SETTINGS (Apply to all connections) types: - # UTC datetimes - datetime: - class: App\Doctrine\Types\UTCDateTimeType - date: - class: App\Doctrine\Types\UTCDateTimeType + datetime: App\Doctrine\Types\UTCDateTimeType + date: App\Doctrine\Types\UTCDateTimeType + datetime_immutable: App\Doctrine\Types\UTCDateTimeImmutableType + date_immutable: App\Doctrine\Types\UTCDateTimeImmutableType + big_decimal: App\Doctrine\Types\BigDecimalType + tinyint: App\Doctrine\Types\TinyIntType - datetime_immutable: - class: App\Doctrine\Types\UTCDateTimeImmutableType - date_immutable: - class: App\Doctrine\Types\UTCDateTimeImmutableType + connections: + default: + use_savepoints: true + schema_filter: ~^(?!internal)~ + url: '%env(resolve:DATABASE_URL)%' - big_decimal: - class: App\Doctrine\Types\BigDecimalType - tinyint: - class: App\Doctrine\Types\TinyIntType - - schema_filter: ~^(?!internal)~ - # Only enable this when needed - profiling_collect_backtrace: false + migration_source: + use_savepoints: true + schema_filter: ~^(?!internal)~ + url: '%env(resolve:DB_MIGRATION_SOURCE)%' orm: auto_generate_proxy_classes: true enable_lazy_ghost_objects: true - report_fields_where_declared: true - validate_xml_mapping: true - naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware - identity_generation_preferences: - Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity - auto_mapping: true - controller_resolver: - auto_mapping: true - mappings: - App: - type: attribute - is_bundle: false - dir: '%kernel.project_dir%/src/Entity' - prefix: 'App\Entity' - alias: App - dql: - string_functions: - regexp: App\Doctrine\Functions\Regexp - field: DoctrineExtensions\Query\Mysql\Field - field2: App\Doctrine\Functions\Field2 - natsort: App\Doctrine\Functions\Natsort - array_position: App\Doctrine\Functions\ArrayPosition - ilike: App\Doctrine\Functions\ILike + entity_managers: + default: &common_orm_settings + report_fields_where_declared: true + validate_xml_mapping: true + naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + identity_generation_preferences: + Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity + mappings: + App: + type: attribute + is_bundle: false + dir: '%kernel.project_dir%/src/Entity' + prefix: 'App\Entity' + alias: App + dql: + string_functions: + regexp: App\Doctrine\Functions\Regexp + field: DoctrineExtensions\Query\Mysql\Field + field2: App\Doctrine\Functions\Field2 + natsort: App\Doctrine\Functions\Natsort + array_position: App\Doctrine\Functions\ArrayPosition + ilike: App\Doctrine\Functions\ILike + connection: default + + migration_source: + <<: *common_orm_settings + connection: migration_source when@test: doctrine: diff --git a/src/Command/Migrations/DBMigrationCommand.php b/src/Command/Migrations/DBMigrationCommand.php new file mode 100644 index 00000000..5cd5808e --- /dev/null +++ b/src/Command/Migrations/DBMigrationCommand.php @@ -0,0 +1,121 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Command\Migrations; + +use App\DataTables\Helpers\ColumnSortHelper; +use App\Entity\Parts\Manufacturer; +use App\Services\ImportExportSystem\PartKeeprImporter\PKImportHelper; +use Doctrine\ORM\EntityManagerInterface; + +use Doctrine\ORM\Id\AssignedGenerator; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\Persistence\ManagerRegistry; +use Doctrine\Persistence\ObjectManager; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +#[AsCommand('partdb:migrate-db', 'Migrate the database to a different platform')] +class DBMigrationCommand extends Command +{ + private readonly EntityManagerInterface $sourceEM; + private readonly EntityManagerInterface $targetEM; + + public function __construct(private readonly ManagerRegistry $managerRegistry, + private readonly PKImportHelper $importHelper, + ) + { + $this->sourceEM = $this->managerRegistry->getManager('migration_source'); + $this->targetEM = $this->managerRegistry->getManager('default'); + + parent::__construct(); + } + + public function execute(InputInterface $input, OutputInterface $output): int + { + // Example migration logic (to be replaced with actual migration code) + $output->writeln('Starting database migration...'); + + //Disable all event listeners on target EM to avoid unwanted side effects + $eventManager = $this->targetEM->getEventManager(); + foreach ($eventManager->getAllListeners() as $event => $listeners) { + foreach ($listeners as $listener) { + $eventManager->removeEventListener($event, $listener); + } + } + + $output->writeln('Clear target database...'); + $this->importHelper->purgeDatabaseForImport($this->targetEM, ['internal', 'migration_versions']); + + $metadata = $this->targetEM->getMetadataFactory()->getAllMetadata(); + + //First we modify each entity metadata to have an persist cascade on all relations + foreach ($metadata as $metadatum) { + $entityClass = $metadatum->getName(); + $output->writeln('Modifying cascade and ID settings for entity: ' . $entityClass); + + foreach ($metadatum->getAssociationNames() as $fieldName) { + $mapping = $metadatum->getAssociationMapping($fieldName); + $mapping->cascade = array_unique(array_merge($mapping->cascade, ['persist'])); + + $metadatum->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE); + $metadatum->setIdGenerator(new AssignedGenerator()); + } + } + + + //Afterwards we migrate all entities + foreach ($metadata as $metadatum) { + //skip all superclasses + if ($metadatum->isMappedSuperclass) { + continue; + } + + $entityClass = $metadatum->getName(); + + $output->writeln('Migrating entity: ' . $entityClass); + + $repo = $this->sourceEM->getRepository($entityClass); + $items = $repo->findAll(); + foreach ($items as $item) { + $this->targetEM->persist($item); + } + $this->targetEM->flush(); + } + + //Migrate all manufacturers from source to target + /*$manufacturerRepo = $this->sourceEM->getRepository(Manufacturer::class); + $manufacturers = $manufacturerRepo->findAll(); + foreach ($manufacturers as $manufacturer) { + $this->targetEM->persist($manufacturer); + } + $this->targetEM->flush(); + */ + + $output->writeln('Database migration completed successfully.'); + + return Command::SUCCESS; + } +} diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelper.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelper.php index f36e48ce..880d77be 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelper.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelper.php @@ -39,10 +39,10 @@ class PKImportHelper * Existing users and groups are not purged. * This is needed to avoid ID collisions. */ - public function purgeDatabaseForImport(): void + public function purgeDatabaseForImport(?EntityManagerInterface $entityManager = null, array $excluded_tables = ['users', 'groups', 'u2f_keys', 'internal', 'migration_versions']): void { //We use the ResetAutoIncrementORMPurger to reset the auto increment values of the tables. Also it normalizes table names before checking for exclusion. - $purger = new ResetAutoIncrementORMPurger($this->em, ['users', 'groups', 'u2f_keys', 'internal', 'migration_versions']); + $purger = new ResetAutoIncrementORMPurger($entityManager ?? $this->em, $excluded_tables); $purger->purge(); }