IBX-11939: [Doctrine Migrations] Added Doctrine Migrations-based schema and data install path (inlined SQL) - #785
IBX-11939: [Doctrine Migrations] Added Doctrine Migrations-based schema and data install path (inlined SQL)#785Steveb-p wants to merge 24 commits into
Conversation
|
Companion PR in |
7114546 to
4e9c810
Compare
Replaces the AbstractVersion + YAML manifest + one-SQL-statement-per-file mechanism with InstallSchemaMigration/ImportDataMigration implemented as plain Doctrine\Migrations\AbstractMigration subclasses with all SQL inlined via addSql(), still implementing IbexaMigrationInterface and tagged with IbexaMigrationTag so TaggedMigrationsRunner/IbexaOnlyDependencyFactory work unchanged. Long/multiline statements use PHP NOWDOC for readability, with CREATE TABLE bodies pretty-printed one column per line. Same conversion as #785 for the 4.6 line, adapted for this branch's ibexa_*-renamed schema and dbal 3.x platform class names (AbstractMySQLPlatform/PostgreSQLPlatform instead of MySqlPlatform/ PostgreSqlPlatform).
Replaces the AbstractVersion + YAML manifest + one-SQL-statement-per-file mechanism with InstallSchemaMigration/ImportDataMigration implemented as plain Doctrine\Migrations\AbstractMigration subclasses with all SQL inlined via addSql(), still implementing IbexaMigrationInterface and tagged with IbexaMigrationTag so TaggedMigrationsRunner/IbexaOnlyDependencyFactory work unchanged. Long/multiline statements use PHP NOWDOC for readability, with CREATE TABLE bodies pretty-printed one column per line. Same conversion as #785 for the 4.6 line, adapted for this branch's ibexa_*-renamed schema and dbal 3.x platform class names (AbstractMySQLPlatform/PostgreSQLPlatform instead of MySqlPlatform/ PostgreSqlPlatform).
Symfony\Component\DependencyInjection\ServiceLocator is not a generic class, so PHPStan rejects the ServiceLocator<Installer> PHPDoc type used for InstallPlatformCommand::$installers. Removed the invalid generic parameter; the type-hint itself (plain ServiceLocator) is unaffected.
Converts InstallSchemaMigration and ImportDataMigration from Ibexa\Contracts\DoctrineMigrations\Migrations\AbstractVersion (which loads its SQL from a YAML manifest + one-statement-per-file pairs at runtime) to Doctrine\Migrations\AbstractMigration directly, with every statement inlined as an addSql() call in up(), branching on $this->platform (MySqlPlatform/PostgreSqlPlatform/SqlitePlatform). Both still implement IbexaMigrationInterface and are tagged with IbexaMigrationTag::TAG in services.yml, so they remain discoverable by TaggedMigrationsRunner/ServiceMigrationsRepository/IbexaOnlyDependencyFactory exactly as before - no other part of the installer changes. Removes the entire Resources/migrations/ YAML+SQL-file tree (509 files), generated from the same schema.yaml/cleandata.sql sources as before.
CREATE TABLE statements are now pretty-printed one column per line, and other long or naturally multi-line statements (multi-row INSERT, long ALTER TABLE ... ADD CONSTRAINT) use NOWDOC instead of a single escaped string literal, for readability. SQL content is unchanged.
Extends AbstractSqlMigration (added in ibexa/doctrine-migrations) instead of the plain Doctrine AbstractMigration, replacing `$this->platform instanceof ...` checks with isMySQL()/isPostgreSQL()/isSqlite(), and moving each platform Statement block out of the PHP file into its own sql/*.sql file loaded via addSqlFile(). Mechanical, content-preserving change: every migration was run before and after against all three platforms and the resulting SQL statement lists are byte-for-byte identical.
Call abortIfUnsupportedPlatform() as the first statement of up(), so installs on a database this migration doesn't build SQL for fail loudly instead of silently queuing zero statements.
Each statement now ends with `;`, matching ibexa:doctrine:schema:dump-sql's own convention, so the files are directly executable via mysql/psql/sqlite3 CLI clients. addSqlFile() still splits on the delimiter and passes one statement per addSql() call, unaffected by the trailing terminator.
…tions Guards ibexa/core's InstallSchemaMigration/ImportDataMigration/ RenameSchemaTo5_0Migration and ibexa/fieldtype-page's RenameSchemaTo5_0Migration so they skip when an existing install already built its schema the old way. ImportDataMigration checks for the absence of its pre-rename input table, since migrations run in a strict, deterministic order -- if it's missing, this system never went through this migration, meaning it built its schema (and bootstrap data) via schema.yaml directly. The two data-value fixes hidden inside the rename migrations (core's ez_lock/ezstring identifier fixes, fieldtype-page's block-ID HTML reference fix) are split into their own always-run migrations (FixLegacyIdentifiersMigration, FixBlockIdReferencesMigration), since a schema-shape guard on the rename would otherwise also skip these on a system whose schema is already renamed but whose row data still holds the old values -- schema.yaml only defines structure, not content. Both run unconditionally right after their corresponding rename (same target version, later creation date), so the tables they touch are always at their final name by then, and each statement is a no-op via its own WHERE clause once already applied.
Doctrine Migrations only calls MetadataStorage::complete() (the write to
doctrine_migration_versions) when a migration's up() returns normally --
never when it throws SkipMigration. So every migration using skipIf() was
being silently re-evaluated on every future doctrine:migrations:migrate
run instead of being permanently recorded as applied, even though its
guard condition (the schema already being in place) never changes back.
Replaces every `$this->skipIf($condition, $message);` with
`if ($condition) { return; }`: up() now returns normally with zero queued
SQL when the guard fires, so the migration is correctly recorded as
executed (with a "did not result in any SQL statements" warning logged,
which is expected and harmless) and never re-evaluated again.
Verified end-to-end: fresh ibexa:install (schema_builder_event enabled)
followed by doctrine:migrations:migrate now records all 44 tagged
migrations in doctrine_migration_versions in one pass, and a second
migrate run does zero work at all ("Already at the latest version").
…name Live end-to-end testing (fresh legacy install -> doctrine:migrations:migrate on 4.6) surfaced that these baseline guards checked the FINAL, post-5.0- rename table name (e.g. "ibexa_content") -- correct on 5.0/6.0, but wrong on 4.6, which has no rename at all: there, the table this migration creates already IS the branch's permanent, current name (e.g. "ezcontentobject"), so the guard never fired and the baseline collided with an already-installed legacy 4.6 schema. Checks the branch's own table name instead, exactly like every other baseline guard that has no later rename to worry about. For core's ImportDataMigration specifically, this required a proper data check rather than a schema-shape check: "ezcontentobject" is 4.6's real, permanent name, so it always exists once the baseline has run there, regardless of whether this migration's own bootstrap INSERTs ran yet. Checks for the actual seed row (root content, id = 1) instead, combined with the table's outright absence (the 5.0/6.0 legacy signal) via OR.
…emaBuilderEvent ibexa:doctrine:migrations:diff (and any other Doctrine Migrations command that needs a target schema) previously failed with 'The schema provider is not available.', since IbexaOnlyDependencyFactory never had an entity manager or SchemaProvider configured. SchemaBuilderEventSchemaProvider bridges Doctrine Migrations' SchemaProvider to Ibexa's own legacy, event-driven schema builder (SchemaBuilderInterface, backed by SchemaBuilderEvent and every installed package's BuildSchemaSubscriber) -- the same mechanism CoreInstaller::importSchema() already uses for the legacy install path. A new compiler pass wires it onto IbexaOnlyDependencyFactory via setService(), a no-op if ibexa/doctrine-schema or ibexa/doctrine-migrations isn't installed.
…nt importData() call) - CoreInstaller::getQueriesFromSchemaBuilderEvent() now builds its Query list via an arrow function instead of a static closure, per review suggestion. - CoreInstaller::importData() no longer re-invokes TaggedMigrationsRunner::run() in the migrations-runner branch; importSchema() already runs every tagged migration (including ImportDataMigration) to the latest version, so the second call was a pure no-op with no real caller relying on it standalone. - getDropSqlStatementsForExistingSchema()'s docblock tightened to list<string>, which it always genuinely returns. - TaggedMigrationsRunner's constructor documents why $dependencyFactory is nullable (optional service, wired via "@?..." in services.yml; run() throws a clear exception if called while null).
ibexa/doctrine-migrations is now published on Packagist (which mirrors all of its branches, not just tags), so the explicit VCS repository pointing composer directly at GitHub is no longer needed to resolve the dev-branch require constraint.
… mandatory Instead of TaggedMigrationsRunner tolerating a null DependencyFactory at runtime, RemoveTaggedMigrationsRunnerPass now removes its service definition entirely when "ibexa/doctrine-migrations" isn't installed/enabled, and CoreInstaller depends on it as an optional (nullable) service instead, throwing a clear configuration exception if "ibexa.installer.schema_builder_event.enabled" is disabled without it.
…erEvent() Passing an empty array as array_map()'s third argument re-indexes its result, so PHPStan can type it as list<Query> instead of Query[].
799cb50 to
cad60ff
Compare
Converts installer's upgrade/db/ibexa-4.5.1-to-4.5.2.sql (indexes on ezcontentobject_link, ezcontentclass_attribute, ezurl_object_link, ezcontentobject_attribute) and upgrade/db/ibexa-4.6.20-to-4.6.21.sql (ezurlalias_ml "link" index) into guarded Doctrine migrations, for installs whose schema already predates these indexes.
$schema->getTable() throws TableDoesNotExist rather than returning null, so a guard that calls it directly (without checking hasTable() first) crashes -- rather than skipping -- on any install that never had the table under this name at all (e.g. a database built directly at the current schema, which never passed through this pre-rename/pre-delta state). Caught via live testing against a real database. Guard now checks hasTable() first.
…into baseline Same rationale as ibexa/product-catalog: doctrine migrations only exist starting at 4.6, so InstallSchemaMigration's baseline already includes these indexes (originally shipped via installer's upgrade/db/ibexa-4.5.0-to-4.5.1.sql). A standalone migration for pre-4.6 content can never have real work to do on a genuine 4.6+ install.
The feat/doctrine-migrations branch it previously required has been merged into and deleted from the primary 4.6 branch upstream, so the old constraint no longer resolves.
$migration->up(new Schema()) gave every migration's hasTable()/ getTable() guard checks an always-empty schema to inspect, regardless of what earlier migrations in the same run (or a prior run) actually created against the real database -- guards written the correct way (checking hasTable() before trusting a table exists) would incorrectly conclude the table is missing and either skip work that still needs doing, or crash outright on getTable() for a table that does exist. Introspect the live database instead, the same way the standalone 'doctrine:migrations:migrate' command already does via DBALSchemaDiffProvider::createFromSchema().
InstallSchemaMigration, AddUrlAliasMlLinkIndexMigration, and ImportDataMigration only depend on the persistence connection -- they don't need anything from IbexaRepositoryInstallerBundle (CoreInstaller, BuildSchemaSubscriber), which is scoped to installer-time concerns and additionally requires DoctrineSchemaBundle to boot at all. Declaring them in IbexaCoreBundle instead matches how every other package in this effort declares its own InstallSchemaMigration in its own bundle, and lets any consumer discover core's schema migrations via ibexa:doctrine:migrations:migrate without needing the installer bundle registered -- useful for integration test kernels that don't otherwise need CoreInstaller.
… SQL install-schema-sqlite.sql and import-data-sqlite.sql were generated from the MySQL source without adapting to SQLite's stricter SQL semantics: - import-data-sqlite.sql used MySQL-style backslash escapes (\" and \n) inside string literals. SQLite (unlike MySQL) does not interpret backslash as an escape character at all, so these were stored as literal two-byte sequences, corrupting PHP-serialized content-type names/descriptions (unserialize() failures) and embedded XML field data (DOMDocument parse failures, e.g. the admin user's own ezimage field). - install-schema-sqlite.sql collapsed the composite primary keys on ezcontentclass, ezcontentclass_attribute and ezcontentobject_attribute (id, version) down to a single-column `id INTEGER PRIMARY KEY AUTOINCREMENT`, dropping the `version` component entirely -- unlike the mysql/postgresql variants, which both correctly declare `PRIMARY KEY(id, version)`. This made SQLite reject the second row (e.g. publishing a content-type draft, which legitimately reuses the same id with a different version/status) as a duplicate primary key. Both only affect the SQLite platform; MySQL and PostgreSQL were already correct.
|
Opened ibexa/doctrine-migrations#4, fixing the |
…nstanceOf ignores Unblocks this PR's CI. Pre-existing on 4.6 itself (unrelated to this PR's changes) - see #794, which fixes it at the source; this commit will become a no-op once that's merged and this branch is rebased on top.
|
Also pushed a temporary fix for the PHPStan baseline drift ( |
|




v4.6Related PRs:
🔄 = 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.Alternative to #784, using the same
IbexaMigrationInterface+IbexaMigrationTag+TaggedMigrationsRunner/IbexaOnlyDependencyFactorymechanism, but withInstallSchemaMigration/ImportDataMigrationimplemented as plainDoctrine\Migrations\AbstractMigrationsubclasses with all SQL inlined directly in PHP (viaaddSql()), instead of Ibexa'sAbstractVersion+ YAML manifest + one-SQL-statement-per-file mechanism. This drops the 509 generated YAML/SQL resource files in favor of two self-contained PHP migration classes.Adds an
ibexa/doctrine-migrations-based alternative to the event-drivenSchemaBuilderEventmechanismCoreInstallercurrently uses to install the core schema and bootstrap data, controlled by a newibexa.installer.schema_builder_event.enabledsetting (defaults totrue, preserving current behavior).CoreInstallerkeeps dispatchingSchemaBuilderEventand importingcleandata.sqldirectly - unchanged.InstallSchemaMigration,ImportDataMigration), tagged withIbexaMigrationTagfor discovery, and executed viaTaggedMigrationsRunnerthroughIbexaOnlyDependencyFactory- an independent Doctrine MigrationsDependencyFactoryscoped to only Ibexa-tagged migrations, so a project's own migrations are never accidentally picked up. Each migration's execution is recorded in the standard Doctrine Migrations versioning table, making repeated installs idempotent.InstallSchemaMigration's SQL (mysql/postgresql/sqlite) is generated from the existingschema.yaml;ImportDataMigration's SQL is generated from the existing per-DBMScleandata.sqlfiles, with the mysql statements also reused verbatim for sqlite (which previously had no cleandata support at all). Long or naturally multi-line statements (CREATE TABLE, multi-rowINSERT, longALTER TABLE ... ADD CONSTRAINT) use PHP NOWDOC for readability; verified byte-for-byte equivalent (aside from whitespace) to the SQL produced by #784.Requires
ibexa/doctrine-migrations(currently tracked via itsfeat/doctrine-migrationsbranch, added as a VCS repository).Checklist:
$ composer fix-cs).@ibexa/engineering).