Repository navigation
IBX-11939: Doctrine migrations (6.0) - #3
Merged
Merged
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This was referenced Jun 29, 2026
Introduce a YAML definition format for declaring SQL statements (inline or from files) with parameters and optional per-database-platform (mysql/postgresql/sqlite) restrictions, loaded via SqlYamlDefinitionLoader and executed through YamlSqlFileMigrationTrait. AbstractVersion now uses this mechanism instead of requiring separate upForMysql()/upForPostgresql() implementations per migration.
Change the YAML SQL definition structure from a flat list to a mapping with "up" and "down" keys (each a list of entries), so migrations can declare their rollback SQL alongside the forward SQL. AbstractVersion now implements down() via the "down" section, throwing IrreversibleMigration when a YAML file declares none.
AbstractVersion is the base class migration authors extend, so it belongs in the public Contracts API rather than the Bundle implementation namespace. It still uses YamlSqlFileMigrationTrait from Bundle to load its YAML-declared SQL.
Superseded by the YAML-based AbstractVersion migration mechanism, which covers per-platform SQL (including MySQL and PostgreSQL) via the "platforms" key on individual YAML entries instead of a dedicated base class per engine.
…able The trait belongs alongside AbstractVersion in the public Contracts API rather than the Bundle implementation namespace. AbstractVersion's up()/down() are no longer final, so subclasses can extend the YAML-declared SQL with additional PHP logic if needed.
Steveb-p
marked this pull request as ready for review
July 16, 2026 08:07
…ations Registers a second, private ServiceMigrationsRepository service (IbexaOnlyMigrationsRepository::SERVICE_ID) that never decorates an inner MigrationsRepository, wired with the same tagged migrations as the default combined one. Consumers that need strictly Ibexa-internal migrations (e.g. installing Ibexa DXP itself) can reference this service by ID instead of the default one, without affecting the default repository's behavior.
Registers ibexa.doctrine_migrations.dependency_factory.ibexa_migrations_only (IbexaOnlyDependencyFactory::SERVICE_ID), a private DependencyFactory built entirely via inline service definitions in services.php as an independent copy of the application's own "doctrine.migrations.dependency_factory" — same Configuration and logger, fetched via inline factory calls — but always running against the "ibexa.persistence.connection" service (created by ibexa/core, so assumed present) and with its MigrationsRepository always the Ibexa-only one. Every other dependency (metadata storage, migrator, plan calculator, ...) is freshly built from those and shared with nothing else, so replacing it never mutates or is affected by the shared application DependencyFactory. Being unfrozen, further dependencies can still be swapped via setService()/setDefinition() before first use. RegisterMigrationsPass resolves the two abstract_arg placeholders (standing in for the application's own DependencyFactory) nested inside the inline Configuration/logger definitions, only when "doctrine.migrations.dependency_factory" is actually present.
The class_exists(newName) ? newName : oldName order made PHPStan (as of phpstan/phpstan 2.2.5) unable to verify the "new name" branch as class-string<AbstractPlatform> whenever only the old-named class is actually installed (e.g. DBAL 2.13.x, which ibexa/core's own dependency constraints currently force in this repo). Checking the class that's actually installed first resolves it, with no behavior change: either order picks the same, correct class for the currently installed DBAL version.
Without this, the install-time TaggedMigrationsRunner used the default alphabetical-by-class comparator instead of ordering migrations by target version and creation date, causing cross-package migrations to run out of order during a full ibexa:install (e.g. a package's baseline running before core's tables it depends on existed).
This was referenced Jul 21, 2026
Merged
Closed
- Updated CI runner OS from ubuntu-24.04 to ubuntu-26.04 in backend-ci.yaml. - Throw LogicException in RegisterMigrationsPass when doctrine.migrations.dependency_factory is missing. - Added @phpstan-assert-if-true type assertion helper in IbexaMigrationComparator to eliminate inline @var annotations.
# Conflicts: # composer.json # phpunit.xml.dist # src/bundle/DependencyInjection/IbexaDoctrineMigrationsExtension.php # src/bundle/IbexaDoctrineMigrationsBundle.php
Introduce a YAML definition format for declaring SQL statements (inline or from files) with parameters and optional per-database-platform (mysql/postgresql/sqlite) restrictions, loaded via SqlYamlDefinitionLoader and executed through YamlSqlFileMigrationTrait. AbstractVersion now uses this mechanism instead of requiring separate upForMysql()/upForPostgresql() implementations per migration.
Change the YAML SQL definition structure from a flat list to a mapping with "up" and "down" keys (each a list of entries), so migrations can declare their rollback SQL alongside the forward SQL. AbstractVersion now implements down() via the "down" section, throwing IrreversibleMigration when a YAML file declares none.
AbstractVersion is the base class migration authors extend, so it belongs in the public Contracts API rather than the Bundle implementation namespace. It still uses YamlSqlFileMigrationTrait from Bundle to load its YAML-declared SQL.
Superseded by the YAML-based AbstractVersion migration mechanism, which covers per-platform SQL (including MySQL and PostgreSQL) via the "platforms" key on individual YAML entries instead of a dedicated base class per engine.
…able The trait belongs alongside AbstractVersion in the public Contracts API rather than the Bundle implementation namespace. AbstractVersion's up()/down() are no longer final, so subclasses can extend the YAML-declared SQL with additional PHP logic if needed.
…helpers and addSqlFile() Adds isMySQL()/isPostgreSQL()/isSqlite()/isPlatform() and addSqlFile() to a new AbstractSqlMigration base class, so per-platform SQL can live in separate .sql files instead of interleaved instanceof-branches in one PHP method. Drops the unused YAML-driven migration mechanism (AbstractVersion, YamlSqlFileMigrationTrait, SqlYamlDefinition*) that nothing in the ecosystem consumed yet, and renames/relocates SqlYamlPlatform to SqlPlatform now that it is no longer YAML-specific.
- Updated CI runner OS from ubuntu-24.04 to ubuntu-26.04 in backend-ci.yaml. - Throw LogicException in RegisterMigrationsPass when doctrine.migrations.dependency_factory is missing. - Added @phpstan-assert-if-true type assertion helper in IbexaMigrationComparator to eliminate inline @var annotations.
…pers getMysqlPlatformClass()/getPostgresqlPlatformClass()/getSqlitePlatformClass() used a class_exists() ternary to pick between DBAL 2 and DBAL 3 platform class names, but this package requires doctrine/dbal ^3.9 on this branch, so the DBAL 2 branch is unreachable. Return the DBAL 3 class directly via ::class so PHPStan can verify the declared class-string<AbstractPlatform> return type statically.
# Conflicts: # composer.json # src/bundle/IbexaDoctrineMigrationsBundle.php
Introduce a YAML definition format for declaring SQL statements (inline or from files) with parameters and optional per-database-platform (mysql/postgresql/sqlite) restrictions, loaded via SqlYamlDefinitionLoader and executed through YamlSqlFileMigrationTrait. AbstractVersion now uses this mechanism instead of requiring separate upForMysql()/upForPostgresql() implementations per migration.
Change the YAML SQL definition structure from a flat list to a mapping with "up" and "down" keys (each a list of entries), so migrations can declare their rollback SQL alongside the forward SQL. AbstractVersion now implements down() via the "down" section, throwing IrreversibleMigration when a YAML file declares none.
AbstractVersion is the base class migration authors extend, so it belongs in the public Contracts API rather than the Bundle implementation namespace. It still uses YamlSqlFileMigrationTrait from Bundle to load its YAML-declared SQL.
Superseded by the YAML-based AbstractVersion migration mechanism, which covers per-platform SQL (including MySQL and PostgreSQL) via the "platforms" key on individual YAML entries instead of a dedicated base class per engine.
…able The trait belongs alongside AbstractVersion in the public Contracts API rather than the Bundle implementation namespace. AbstractVersion's up()/down() are no longer final, so subclasses can extend the YAML-declared SQL with additional PHP logic if needed.
…helpers and addSqlFile() Adds isMySQL()/isPostgreSQL()/isSqlite()/isPlatform() and addSqlFile() to a new AbstractSqlMigration base class, so per-platform SQL can live in separate .sql files instead of interleaved instanceof-branches in one PHP method. Drops the unused YAML-driven migration mechanism (AbstractVersion, YamlSqlFileMigrationTrait, SqlYamlDefinition*) that nothing in the ecosystem consumed yet, and renames/relocates SqlYamlPlatform to SqlPlatform now that it is no longer YAML-specific.
- Updated CI runner OS from ubuntu-24.04 to ubuntu-26.04 in backend-ci.yaml. - Throw LogicException in RegisterMigrationsPass when doctrine.migrations.dependency_factory is missing. - Added @phpstan-assert-if-true type assertion helper in IbexaMigrationComparator to eliminate inline @var annotations.
Several rebased commits' own diffs against backend-ci.yaml re-introduced the 4.6-specific ubuntu-26.04 runner version during auto-merge (no conflict was raised since the context lines matched). Restored ubuntu-24.04, matching plain 6.0's own CI config.
Steveb-p
force-pushed
the
feat/doctrine-migrations-6.0
branch
from
July 24, 2026 15:55
7c6173b to
279292c
Compare
Propagated from feat/doctrine-migrations-5.0. 5.0/6.0 require PHP >=8.3, so SqlPlatform's string constants become a backed enum instead. Every consumer package only ever references SqlPlatform::MYSQL/POSTGRESQL/SQLITE as opaque tokens (never as raw strings), so this is a purely internal type change - confirmed against ibexa/scheduler via a temporary path-repository pin, whose own phpstan and test suite both pass unchanged against this branch. AbstractSqlMigration::isPlatform()/abortIfUnsupportedPlatform() and DatabasePlatformResolver::resolve() retyped from string to SqlPlatform; abortIfUnsupportedPlatform()'s error message now maps through SqlPlatform::value since implode() can't stringify enum cases directly. 4.6 keeps the plain string-constant class (PHP 7.4 doesn't support enums).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Warning
#1 (4.6) and #2 (5.0) have both landed (#1 merged, #2 closed after landing directly on
5.0), so this PR's base is now the real6.0branch itself rather than the temporarybase/ibx-11939-4.6-5.0-merged-6.0scaffold.Warning
This is the 6.0 follow-up to #2 (5.0). Review that PR first — this carries the same migration forward onto this branch.
Related PRs:
#168⏭️#40⏭️🔄 = this branch adds an upgrade migration (renames/FK-retargets existing schema), not just a fresh baseline.
⚠️ = fixes a different, related problem (missing
🩹 = this branch also received a backported delta migration, converted from a legacy
ibexa/installerupgrade/db/*.sqlscript (4.6.0 or later) that the original baseline-only migration didn't cover. See IBX-11939 upgrade-scripts backport report or that package's own PR description for details.SchemaBuilderEventsupport for a plain Doctrine ORM entity/table) — not a Doctrine Migrations baseline/upgrade migration like the rest of this table. See that package's own PR description for details.Description:
Adds a Doctrine Migrations integration for Ibexa-internal migrations, kept entirely separate
from the project's own migrations.
1. An independent, Ibexa-only migration set
IbexaOnlyMigrationsRepository/IbexaOnlyDependencyFactoryprovide aDependencyFactorythatbehaves like the application's own
doctrine.migrations.dependency_factory(sameConfigurationand logger) except it always runs against the
ibexa.persistence.connectionservice and onlyever sees migrations tagged with
IbexaMigrationTag::TAG.RegisterMigrationsPasswires taggedmigration services into this repository at compile time and nothing else — the application's own
doctrine.migrations.dependency_factoryis never touched, so Ibexa's migrations never appear inthe project's own
doctrine:migrations:*commands.2. Write a migration
Extend
AbstractSqlMigrationfor per-platform SQL, and implementIbexaMigrationInterfaceso themigration is ordered correctly (by target Ibexa version, then creation date) relative to other
Ibexa migrations:
AbstractSqlMigration(extends Doctrine's ownAbstractMigration) provides:isMySQL()/isPostgreSQL()/isSqlite()/isPlatform(string $platform)— named checksinstead of raw
$this->platform instanceof ..., backed bySqlPlatform's string constants.addSqlFile(string $file, string $delimiter = self::DEFAULT_SQL_STATEMENT_DELIMITER)— loadsone or more SQL statements from an external file (separated by a delimiter line) and queues each
one via
addSql(), so a platform's SQL can live in its own.sqlfile instead of a PHP heredoc.abortIfUnsupportedPlatform(string ...$supportedPlatforms)— aborts the migration with a clearmessage on any platform not listed, instead of silently queuing zero SQL and being recorded as
successfully executed.
3. Register it
autoconfigure: falsematters: if the application enables Doctrine's owndoctrine_migrations.enable_service_migrationsoption, an autoconfiguredAbstractMigrationsubclass gets auto-tagged
doctrine_migrations.migrationby Doctrine's own bundle — leaking itinto the project's own migration view regardless of the tag above.
4.
ibexa:doctrine:migrations:*console commandsA full parallel copy of every
doctrine:migrations:*command (migrate,status,execute,generate,latest,up-to-date,diff,dump-schema,rollup,sync-metadata-storage,version,current,versions), each constructed againstIbexaOnlyDependencyFactoryinstead ofthe application's own factory — so e.g.
ibexa:doctrine:migrations:statusreports only Ibexa'stagged migrations, while
doctrine:migrations:statuscontinues to report only the project's own.Every
doctrine/migrationscommand class shares the sameDoctrineCommand::__construct(?DependencyFactory, ?string $name)constructor, so no new commandclasses were needed — only new service definitions pointing at a different
DependencyFactory.For QA:
Run the existing suites:
composer test,composer test-integration,composer phpstan,composer check-cs.To verify the separation end-to-end in a real app:
The first should list Ibexa's tagged migrations (e.g. this package's own test/fixture migrations,
or any real consumer package's migrations if installed); the second should show none of them.
Documentation:
See the usage examples in the Description above.
AbstractSqlMigration's class docblock(
src/contracts/Migrations/AbstractSqlMigration.php) is the authoritative reference for its helpermethods.