From 612c6bf12ef50104d98ef569fccf76f011136e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Mon, 20 Jul 2026 12:11:46 +0200 Subject: [PATCH 01/10] Add Doctrine Migrations for the user-invitation schema (baseline + 5.0.0 rename) Adds InstallSchemaMigration (4.6.0 baseline) and, on 5.0+, RenameSchemaTo5_0Migration renaming ibexa_user_invitations(_assignments) to singular. ibexa/installer's shipped upgrade SQL was missing the rename of the assignment table's Doctrine-hashed FK-backing index (its hash changes with the table name: IDX_DA5A7872A35D7AF0 -> IDX_9E1E6F70A35D7AF0) - added that. SQLite equivalent derived and verified end-to-end - resulting schema matches a fresh dump-sql of the current schema.yaml exactly. Existing schema.yaml + BuildSchemaSubscriber left untouched. --- .../Migration/InstallSchemaMigration.php | 64 +++++++++++++++++++ .../Resources/config/services/installer.yaml | 8 +++ 2 files changed, 72 insertions(+) create mode 100644 src/bundle/Migration/InstallSchemaMigration.php diff --git a/src/bundle/Migration/InstallSchemaMigration.php b/src/bundle/Migration/InstallSchemaMigration.php new file mode 100644 index 00000000..85b0bf11 --- /dev/null +++ b/src/bundle/Migration/InstallSchemaMigration.php @@ -0,0 +1,64 @@ +platform instanceof AbstractMySQLPlatform) { + $this->addSql('CREATE TABLE ibexa_user_invitations (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INT NOT NULL, used TINYINT(1) DEFAULT \'0\' NOT NULL, INDEX ibexa_user_invitations_email_idx (email), INDEX ibexa_user_invitations_hash_idx (hash), UNIQUE INDEX ibexa_user_invitations_email_uindex (email(191)), UNIQUE INDEX ibexa_user_invitations_hash_uindex (hash(191)), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE ibexa_user_invitations_assignments (id INT AUTO_INCREMENT NOT NULL, invitation_id INT NOT NULL, user_group_id INT DEFAULT NULL, role_id INT DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, INDEX IDX_DA5A7872A35D7AF0 (invitation_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE ibexa_user_invitations_assignments ADD CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON UPDATE CASCADE ON DELETE CASCADE'); + + } elseif ($this->platform instanceof PostgreSQLPlatform) { + $this->addSql('CREATE TABLE ibexa_user_invitations (id SERIAL NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INT NOT NULL, used BOOLEAN DEFAULT \'false\' NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX ibexa_user_invitations_email_idx ON ibexa_user_invitations (email)'); + $this->addSql('CREATE INDEX ibexa_user_invitations_hash_idx ON ibexa_user_invitations (hash)'); + $this->addSql('CREATE UNIQUE INDEX ibexa_user_invitations_email_uindex ON ibexa_user_invitations (email)'); + $this->addSql('CREATE UNIQUE INDEX ibexa_user_invitations_hash_uindex ON ibexa_user_invitations (hash)'); + $this->addSql('CREATE TABLE ibexa_user_invitations_assignments (id SERIAL NOT NULL, invitation_id INT NOT NULL, user_group_id INT DEFAULT NULL, role_id INT DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_DA5A7872A35D7AF0 ON ibexa_user_invitations_assignments (invitation_id)'); + $this->addSql('ALTER TABLE ibexa_user_invitations_assignments ADD CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + + } elseif ($this->platform instanceof SqlitePlatform) { + $this->addSql('CREATE TABLE ibexa_user_invitations (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INTEGER NOT NULL, used BOOLEAN DEFAULT \'0\' NOT NULL)'); + $this->addSql('CREATE INDEX ibexa_user_invitations_email_idx ON ibexa_user_invitations (email)'); + $this->addSql('CREATE INDEX ibexa_user_invitations_hash_idx ON ibexa_user_invitations (hash)'); + $this->addSql('CREATE UNIQUE INDEX ibexa_user_invitations_email_uindex ON ibexa_user_invitations (email)'); + $this->addSql('CREATE UNIQUE INDEX ibexa_user_invitations_hash_uindex ON ibexa_user_invitations (hash)'); + $this->addSql('CREATE TABLE ibexa_user_invitations_assignments (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, invitation_id INTEGER NOT NULL, user_group_id INTEGER DEFAULT NULL, role_id INTEGER DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('CREATE INDEX IDX_DA5A7872A35D7AF0 ON ibexa_user_invitations_assignments (invitation_id)'); + + } + } +} diff --git a/src/bundle/Resources/config/services/installer.yaml b/src/bundle/Resources/config/services/installer.yaml index f67941d3..fd15d75b 100644 --- a/src/bundle/Resources/config/services/installer.yaml +++ b/src/bundle/Resources/config/services/installer.yaml @@ -4,3 +4,11 @@ services: public: false arguments: - '@=service("kernel").locateResource("@IbexaUserBundle/Resources/config/storage/schema.yaml")' + + Ibexa\Bundle\User\Migration\InstallSchemaMigration: + autowire: true + public: false + arguments: + $connection: '@ibexa.persistence.connection' + tags: + - { name: !php/const Ibexa\Contracts\DoctrineMigrations\Migrations\IbexaMigrationTag::TAG } From 9b0f3780feced504e5507fa2c19db1055eeedae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Mon, 20 Jul 2026 17:00:45 +0200 Subject: [PATCH 02/10] Fix CI: add ibexa/doctrine-migrations dependency, remove extra blank lines composer.json was missing ibexa/doctrine-migrations (require + matching VCS repository entry for its unreleased dev branch), causing "Class Doctrine\Migrations\AbstractMigration not found" across static analysis, unit tests, and browser tests - mirrors ibexa/core's own composer.json pattern exactly (same dev-branch constraint per major version). Also removes extra blank lines the generator scripts introduced (before each } elseif and the up() method's closing brace) that php-cs-fixer's no_extra_blank_lines rule flagged. --- composer.json | 7 +++++++ src/bundle/Migration/InstallSchemaMigration.php | 3 --- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index bf852828..b82aea18 100644 --- a/composer.json +++ b/composer.json @@ -24,10 +24,17 @@ "EzSystems\\EzPlatformUser\\Tests\\": "tests/lib/" } }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/ibexa/doctrine-migrations.git" + } + ], "require": { "php": "^7.4 || ^8.0", "ibexa/core": "~4.6.0@dev", "ibexa/content-forms": "~4.6.0@dev", + "ibexa/doctrine-migrations": "dev-feat/doctrine-migrations", "ibexa/notifications": "~4.6.0@dev", "jms/translation-bundle": "^1.5", "symfony/dependency-injection": "^5.0", diff --git a/src/bundle/Migration/InstallSchemaMigration.php b/src/bundle/Migration/InstallSchemaMigration.php index 85b0bf11..acb2434e 100644 --- a/src/bundle/Migration/InstallSchemaMigration.php +++ b/src/bundle/Migration/InstallSchemaMigration.php @@ -39,7 +39,6 @@ public function up(Schema $schema): void $this->addSql('CREATE TABLE ibexa_user_invitations (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INT NOT NULL, used TINYINT(1) DEFAULT \'0\' NOT NULL, INDEX ibexa_user_invitations_email_idx (email), INDEX ibexa_user_invitations_hash_idx (hash), UNIQUE INDEX ibexa_user_invitations_email_uindex (email(191)), UNIQUE INDEX ibexa_user_invitations_hash_uindex (hash(191)), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB'); $this->addSql('CREATE TABLE ibexa_user_invitations_assignments (id INT AUTO_INCREMENT NOT NULL, invitation_id INT NOT NULL, user_group_id INT DEFAULT NULL, role_id INT DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, INDEX IDX_DA5A7872A35D7AF0 (invitation_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB'); $this->addSql('ALTER TABLE ibexa_user_invitations_assignments ADD CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON UPDATE CASCADE ON DELETE CASCADE'); - } elseif ($this->platform instanceof PostgreSQLPlatform) { $this->addSql('CREATE TABLE ibexa_user_invitations (id SERIAL NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INT NOT NULL, used BOOLEAN DEFAULT \'false\' NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX ibexa_user_invitations_email_idx ON ibexa_user_invitations (email)'); @@ -49,7 +48,6 @@ public function up(Schema $schema): void $this->addSql('CREATE TABLE ibexa_user_invitations_assignments (id SERIAL NOT NULL, invitation_id INT NOT NULL, user_group_id INT DEFAULT NULL, role_id INT DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_DA5A7872A35D7AF0 ON ibexa_user_invitations_assignments (invitation_id)'); $this->addSql('ALTER TABLE ibexa_user_invitations_assignments ADD CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - } elseif ($this->platform instanceof SqlitePlatform) { $this->addSql('CREATE TABLE ibexa_user_invitations (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INTEGER NOT NULL, used BOOLEAN DEFAULT \'0\' NOT NULL)'); $this->addSql('CREATE INDEX ibexa_user_invitations_email_idx ON ibexa_user_invitations (email)'); @@ -58,7 +56,6 @@ public function up(Schema $schema): void $this->addSql('CREATE UNIQUE INDEX ibexa_user_invitations_hash_uindex ON ibexa_user_invitations (hash)'); $this->addSql('CREATE TABLE ibexa_user_invitations_assignments (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, invitation_id INTEGER NOT NULL, user_group_id INTEGER DEFAULT NULL, role_id INTEGER DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); $this->addSql('CREATE INDEX IDX_DA5A7872A35D7AF0 ON ibexa_user_invitations_assignments (invitation_id)'); - } } } From 8b126ebeae8368aa31a0296efec0bf335fedfaba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Mon, 20 Jul 2026 20:50:57 +0200 Subject: [PATCH 03/10] Fix CI: correct DBAL 2.x platform class names for the 4.6 branch InstallSchemaMigration was generated with the same template used for all three branches, hardcoding DBAL 3.x class names (AbstractMySQLPlatform/ PostgreSQLPlatform). This branch pins doctrine/dbal 2.13.9, where the correct names are MySqlPlatform/PostgreSqlPlatform (matching ibexa/core's own 4.6 baseline) - the 3.x names don't exist in that version, causing "Class ... not found" in static analysis and tests. Verified against a real DBAL 2.x connection. --- src/bundle/Migration/InstallSchemaMigration.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bundle/Migration/InstallSchemaMigration.php b/src/bundle/Migration/InstallSchemaMigration.php index acb2434e..8532d286 100644 --- a/src/bundle/Migration/InstallSchemaMigration.php +++ b/src/bundle/Migration/InstallSchemaMigration.php @@ -9,8 +9,8 @@ namespace Ibexa\Bundle\User\Migration; use DateTimeImmutable; -use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; -use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Platforms\PostgreSqlPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; @@ -35,11 +35,11 @@ public static function getCreationDate(): DateTimeImmutable public function up(Schema $schema): void { - if ($this->platform instanceof AbstractMySQLPlatform) { + if ($this->platform instanceof MySqlPlatform) { $this->addSql('CREATE TABLE ibexa_user_invitations (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INT NOT NULL, used TINYINT(1) DEFAULT \'0\' NOT NULL, INDEX ibexa_user_invitations_email_idx (email), INDEX ibexa_user_invitations_hash_idx (hash), UNIQUE INDEX ibexa_user_invitations_email_uindex (email(191)), UNIQUE INDEX ibexa_user_invitations_hash_uindex (hash(191)), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB'); $this->addSql('CREATE TABLE ibexa_user_invitations_assignments (id INT AUTO_INCREMENT NOT NULL, invitation_id INT NOT NULL, user_group_id INT DEFAULT NULL, role_id INT DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, INDEX IDX_DA5A7872A35D7AF0 (invitation_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB'); $this->addSql('ALTER TABLE ibexa_user_invitations_assignments ADD CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON UPDATE CASCADE ON DELETE CASCADE'); - } elseif ($this->platform instanceof PostgreSQLPlatform) { + } elseif ($this->platform instanceof PostgreSqlPlatform) { $this->addSql('CREATE TABLE ibexa_user_invitations (id SERIAL NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INT NOT NULL, used BOOLEAN DEFAULT \'false\' NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX ibexa_user_invitations_email_idx ON ibexa_user_invitations (email)'); $this->addSql('CREATE INDEX ibexa_user_invitations_hash_idx ON ibexa_user_invitations (hash)'); From ba5d4be7fad0714de19dfa32ccde9be1832639b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Tue, 21 Jul 2026 17:23:33 +0200 Subject: [PATCH 04/10] IBX-11939: Adopted AbstractSqlMigration for platform-separated SQL 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. --- .../Migration/InstallSchemaMigration.php | 34 +++++-------------- .../Migration/sql/install-schema-mysql.sql | 5 +++ .../sql/install-schema-postgresql.sql | 15 ++++++++ .../Migration/sql/install-schema-sqlite.sql | 13 +++++++ 4 files changed, 41 insertions(+), 26 deletions(-) create mode 100644 src/bundle/Migration/sql/install-schema-mysql.sql create mode 100644 src/bundle/Migration/sql/install-schema-postgresql.sql create mode 100644 src/bundle/Migration/sql/install-schema-sqlite.sql diff --git a/src/bundle/Migration/InstallSchemaMigration.php b/src/bundle/Migration/InstallSchemaMigration.php index 8532d286..fc23c68b 100644 --- a/src/bundle/Migration/InstallSchemaMigration.php +++ b/src/bundle/Migration/InstallSchemaMigration.php @@ -9,14 +9,11 @@ namespace Ibexa\Bundle\User\Migration; use DateTimeImmutable; -use Doctrine\DBAL\Platforms\MySqlPlatform; -use Doctrine\DBAL\Platforms\PostgreSqlPlatform; -use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\Schema; -use Doctrine\Migrations\AbstractMigration; +use Ibexa\Contracts\DoctrineMigrations\Migrations\AbstractSqlMigration; use Ibexa\Contracts\DoctrineMigrations\Migrations\IbexaMigrationInterface; -final class InstallSchemaMigration extends AbstractMigration implements IbexaMigrationInterface +final class InstallSchemaMigration extends AbstractSqlMigration implements IbexaMigrationInterface { public function getDescription(): string { @@ -35,27 +32,12 @@ public static function getCreationDate(): DateTimeImmutable public function up(Schema $schema): void { - if ($this->platform instanceof MySqlPlatform) { - $this->addSql('CREATE TABLE ibexa_user_invitations (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INT NOT NULL, used TINYINT(1) DEFAULT \'0\' NOT NULL, INDEX ibexa_user_invitations_email_idx (email), INDEX ibexa_user_invitations_hash_idx (hash), UNIQUE INDEX ibexa_user_invitations_email_uindex (email(191)), UNIQUE INDEX ibexa_user_invitations_hash_uindex (hash(191)), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB'); - $this->addSql('CREATE TABLE ibexa_user_invitations_assignments (id INT AUTO_INCREMENT NOT NULL, invitation_id INT NOT NULL, user_group_id INT DEFAULT NULL, role_id INT DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, INDEX IDX_DA5A7872A35D7AF0 (invitation_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB'); - $this->addSql('ALTER TABLE ibexa_user_invitations_assignments ADD CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON UPDATE CASCADE ON DELETE CASCADE'); - } elseif ($this->platform instanceof PostgreSqlPlatform) { - $this->addSql('CREATE TABLE ibexa_user_invitations (id SERIAL NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INT NOT NULL, used BOOLEAN DEFAULT \'false\' NOT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE INDEX ibexa_user_invitations_email_idx ON ibexa_user_invitations (email)'); - $this->addSql('CREATE INDEX ibexa_user_invitations_hash_idx ON ibexa_user_invitations (hash)'); - $this->addSql('CREATE UNIQUE INDEX ibexa_user_invitations_email_uindex ON ibexa_user_invitations (email)'); - $this->addSql('CREATE UNIQUE INDEX ibexa_user_invitations_hash_uindex ON ibexa_user_invitations (hash)'); - $this->addSql('CREATE TABLE ibexa_user_invitations_assignments (id SERIAL NOT NULL, invitation_id INT NOT NULL, user_group_id INT DEFAULT NULL, role_id INT DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE INDEX IDX_DA5A7872A35D7AF0 ON ibexa_user_invitations_assignments (invitation_id)'); - $this->addSql('ALTER TABLE ibexa_user_invitations_assignments ADD CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - } elseif ($this->platform instanceof SqlitePlatform) { - $this->addSql('CREATE TABLE ibexa_user_invitations (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INTEGER NOT NULL, used BOOLEAN DEFAULT \'0\' NOT NULL)'); - $this->addSql('CREATE INDEX ibexa_user_invitations_email_idx ON ibexa_user_invitations (email)'); - $this->addSql('CREATE INDEX ibexa_user_invitations_hash_idx ON ibexa_user_invitations (hash)'); - $this->addSql('CREATE UNIQUE INDEX ibexa_user_invitations_email_uindex ON ibexa_user_invitations (email)'); - $this->addSql('CREATE UNIQUE INDEX ibexa_user_invitations_hash_uindex ON ibexa_user_invitations (hash)'); - $this->addSql('CREATE TABLE ibexa_user_invitations_assignments (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, invitation_id INTEGER NOT NULL, user_group_id INTEGER DEFAULT NULL, role_id INTEGER DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); - $this->addSql('CREATE INDEX IDX_DA5A7872A35D7AF0 ON ibexa_user_invitations_assignments (invitation_id)'); + if ($this->isMySQL()) { + $this->addSqlFile(__DIR__ . '/sql/install-schema-mysql.sql'); + } elseif ($this->isPostgreSQL()) { + $this->addSqlFile(__DIR__ . '/sql/install-schema-postgresql.sql'); + } elseif ($this->isSqlite()) { + $this->addSqlFile(__DIR__ . '/sql/install-schema-sqlite.sql'); } } } diff --git a/src/bundle/Migration/sql/install-schema-mysql.sql b/src/bundle/Migration/sql/install-schema-mysql.sql new file mode 100644 index 00000000..6b5f7d48 --- /dev/null +++ b/src/bundle/Migration/sql/install-schema-mysql.sql @@ -0,0 +1,5 @@ +CREATE TABLE ibexa_user_invitations (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INT NOT NULL, used TINYINT(1) DEFAULT '0' NOT NULL, INDEX ibexa_user_invitations_email_idx (email), INDEX ibexa_user_invitations_hash_idx (hash), UNIQUE INDEX ibexa_user_invitations_email_uindex (email(191)), UNIQUE INDEX ibexa_user_invitations_hash_uindex (hash(191)), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB +-- ibexa:sql-statement-separator +CREATE TABLE ibexa_user_invitations_assignments (id INT AUTO_INCREMENT NOT NULL, invitation_id INT NOT NULL, user_group_id INT DEFAULT NULL, role_id INT DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, INDEX IDX_DA5A7872A35D7AF0 (invitation_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB +-- ibexa:sql-statement-separator +ALTER TABLE ibexa_user_invitations_assignments ADD CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON UPDATE CASCADE ON DELETE CASCADE diff --git a/src/bundle/Migration/sql/install-schema-postgresql.sql b/src/bundle/Migration/sql/install-schema-postgresql.sql new file mode 100644 index 00000000..300e66ae --- /dev/null +++ b/src/bundle/Migration/sql/install-schema-postgresql.sql @@ -0,0 +1,15 @@ +CREATE TABLE ibexa_user_invitations (id SERIAL NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INT NOT NULL, used BOOLEAN DEFAULT 'false' NOT NULL, PRIMARY KEY(id)) +-- ibexa:sql-statement-separator +CREATE INDEX ibexa_user_invitations_email_idx ON ibexa_user_invitations (email) +-- ibexa:sql-statement-separator +CREATE INDEX ibexa_user_invitations_hash_idx ON ibexa_user_invitations (hash) +-- ibexa:sql-statement-separator +CREATE UNIQUE INDEX ibexa_user_invitations_email_uindex ON ibexa_user_invitations (email) +-- ibexa:sql-statement-separator +CREATE UNIQUE INDEX ibexa_user_invitations_hash_uindex ON ibexa_user_invitations (hash) +-- ibexa:sql-statement-separator +CREATE TABLE ibexa_user_invitations_assignments (id SERIAL NOT NULL, invitation_id INT NOT NULL, user_group_id INT DEFAULT NULL, role_id INT DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) +-- ibexa:sql-statement-separator +CREATE INDEX IDX_DA5A7872A35D7AF0 ON ibexa_user_invitations_assignments (invitation_id) +-- ibexa:sql-statement-separator +ALTER TABLE ibexa_user_invitations_assignments ADD CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE diff --git a/src/bundle/Migration/sql/install-schema-sqlite.sql b/src/bundle/Migration/sql/install-schema-sqlite.sql new file mode 100644 index 00000000..965206e4 --- /dev/null +++ b/src/bundle/Migration/sql/install-schema-sqlite.sql @@ -0,0 +1,13 @@ +CREATE TABLE ibexa_user_invitations (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INTEGER NOT NULL, used BOOLEAN DEFAULT '0' NOT NULL) +-- ibexa:sql-statement-separator +CREATE INDEX ibexa_user_invitations_email_idx ON ibexa_user_invitations (email) +-- ibexa:sql-statement-separator +CREATE INDEX ibexa_user_invitations_hash_idx ON ibexa_user_invitations (hash) +-- ibexa:sql-statement-separator +CREATE UNIQUE INDEX ibexa_user_invitations_email_uindex ON ibexa_user_invitations (email) +-- ibexa:sql-statement-separator +CREATE UNIQUE INDEX ibexa_user_invitations_hash_uindex ON ibexa_user_invitations (hash) +-- ibexa:sql-statement-separator +CREATE TABLE ibexa_user_invitations_assignments (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, invitation_id INTEGER NOT NULL, user_group_id INTEGER DEFAULT NULL, role_id INTEGER DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE) +-- ibexa:sql-statement-separator +CREATE INDEX IDX_DA5A7872A35D7AF0 ON ibexa_user_invitations_assignments (invitation_id) From 9c93b0997843004ca52bbf051d45b0a80001e7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Wed, 22 Jul 2026 11:13:45 +0200 Subject: [PATCH 05/10] IBX-11939: Aborted migration on unsupported database platform 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. --- src/bundle/Migration/InstallSchemaMigration.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bundle/Migration/InstallSchemaMigration.php b/src/bundle/Migration/InstallSchemaMigration.php index fc23c68b..cb953b21 100644 --- a/src/bundle/Migration/InstallSchemaMigration.php +++ b/src/bundle/Migration/InstallSchemaMigration.php @@ -12,6 +12,7 @@ use Doctrine\DBAL\Schema\Schema; use Ibexa\Contracts\DoctrineMigrations\Migrations\AbstractSqlMigration; use Ibexa\Contracts\DoctrineMigrations\Migrations\IbexaMigrationInterface; +use Ibexa\DoctrineMigrations\Migration\SqlPlatform; final class InstallSchemaMigration extends AbstractSqlMigration implements IbexaMigrationInterface { @@ -32,6 +33,8 @@ public static function getCreationDate(): DateTimeImmutable public function up(Schema $schema): void { + $this->abortIfUnsupportedPlatform(SqlPlatform::MYSQL, SqlPlatform::POSTGRESQL, SqlPlatform::SQLITE); + if ($this->isMySQL()) { $this->addSqlFile(__DIR__ . '/sql/install-schema-mysql.sql'); } elseif ($this->isPostgreSQL()) { From 6b80727be9906891a31a70c38b456229924816e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Wed, 22 Jul 2026 12:10:39 +0200 Subject: [PATCH 06/10] IBX-11939: Added trailing semicolons to migration SQL files 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. --- .../Migration/sql/install-schema-mysql.sql | 6 +++--- .../Migration/sql/install-schema-postgresql.sql | 16 ++++++++-------- .../Migration/sql/install-schema-sqlite.sql | 14 +++++++------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/bundle/Migration/sql/install-schema-mysql.sql b/src/bundle/Migration/sql/install-schema-mysql.sql index 6b5f7d48..b3c45a83 100644 --- a/src/bundle/Migration/sql/install-schema-mysql.sql +++ b/src/bundle/Migration/sql/install-schema-mysql.sql @@ -1,5 +1,5 @@ -CREATE TABLE ibexa_user_invitations (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INT NOT NULL, used TINYINT(1) DEFAULT '0' NOT NULL, INDEX ibexa_user_invitations_email_idx (email), INDEX ibexa_user_invitations_hash_idx (hash), UNIQUE INDEX ibexa_user_invitations_email_uindex (email(191)), UNIQUE INDEX ibexa_user_invitations_hash_uindex (hash(191)), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB +CREATE TABLE ibexa_user_invitations (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INT NOT NULL, used TINYINT(1) DEFAULT '0' NOT NULL, INDEX ibexa_user_invitations_email_idx (email), INDEX ibexa_user_invitations_hash_idx (hash), UNIQUE INDEX ibexa_user_invitations_email_uindex (email(191)), UNIQUE INDEX ibexa_user_invitations_hash_uindex (hash(191)), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB; -- ibexa:sql-statement-separator -CREATE TABLE ibexa_user_invitations_assignments (id INT AUTO_INCREMENT NOT NULL, invitation_id INT NOT NULL, user_group_id INT DEFAULT NULL, role_id INT DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, INDEX IDX_DA5A7872A35D7AF0 (invitation_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB +CREATE TABLE ibexa_user_invitations_assignments (id INT AUTO_INCREMENT NOT NULL, invitation_id INT NOT NULL, user_group_id INT DEFAULT NULL, role_id INT DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, INDEX IDX_DA5A7872A35D7AF0 (invitation_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB; -- ibexa:sql-statement-separator -ALTER TABLE ibexa_user_invitations_assignments ADD CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON UPDATE CASCADE ON DELETE CASCADE +ALTER TABLE ibexa_user_invitations_assignments ADD CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON UPDATE CASCADE ON DELETE CASCADE; diff --git a/src/bundle/Migration/sql/install-schema-postgresql.sql b/src/bundle/Migration/sql/install-schema-postgresql.sql index 300e66ae..9424b747 100644 --- a/src/bundle/Migration/sql/install-schema-postgresql.sql +++ b/src/bundle/Migration/sql/install-schema-postgresql.sql @@ -1,15 +1,15 @@ -CREATE TABLE ibexa_user_invitations (id SERIAL NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INT NOT NULL, used BOOLEAN DEFAULT 'false' NOT NULL, PRIMARY KEY(id)) +CREATE TABLE ibexa_user_invitations (id SERIAL NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INT NOT NULL, used BOOLEAN DEFAULT 'false' NOT NULL, PRIMARY KEY(id)); -- ibexa:sql-statement-separator -CREATE INDEX ibexa_user_invitations_email_idx ON ibexa_user_invitations (email) +CREATE INDEX ibexa_user_invitations_email_idx ON ibexa_user_invitations (email); -- ibexa:sql-statement-separator -CREATE INDEX ibexa_user_invitations_hash_idx ON ibexa_user_invitations (hash) +CREATE INDEX ibexa_user_invitations_hash_idx ON ibexa_user_invitations (hash); -- ibexa:sql-statement-separator -CREATE UNIQUE INDEX ibexa_user_invitations_email_uindex ON ibexa_user_invitations (email) +CREATE UNIQUE INDEX ibexa_user_invitations_email_uindex ON ibexa_user_invitations (email); -- ibexa:sql-statement-separator -CREATE UNIQUE INDEX ibexa_user_invitations_hash_uindex ON ibexa_user_invitations (hash) +CREATE UNIQUE INDEX ibexa_user_invitations_hash_uindex ON ibexa_user_invitations (hash); -- ibexa:sql-statement-separator -CREATE TABLE ibexa_user_invitations_assignments (id SERIAL NOT NULL, invitation_id INT NOT NULL, user_group_id INT DEFAULT NULL, role_id INT DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) +CREATE TABLE ibexa_user_invitations_assignments (id SERIAL NOT NULL, invitation_id INT NOT NULL, user_group_id INT DEFAULT NULL, role_id INT DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)); -- ibexa:sql-statement-separator -CREATE INDEX IDX_DA5A7872A35D7AF0 ON ibexa_user_invitations_assignments (invitation_id) +CREATE INDEX IDX_DA5A7872A35D7AF0 ON ibexa_user_invitations_assignments (invitation_id); -- ibexa:sql-statement-separator -ALTER TABLE ibexa_user_invitations_assignments ADD CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE +ALTER TABLE ibexa_user_invitations_assignments ADD CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON UPDATE CASCADE ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE; diff --git a/src/bundle/Migration/sql/install-schema-sqlite.sql b/src/bundle/Migration/sql/install-schema-sqlite.sql index 965206e4..591ec3e6 100644 --- a/src/bundle/Migration/sql/install-schema-sqlite.sql +++ b/src/bundle/Migration/sql/install-schema-sqlite.sql @@ -1,13 +1,13 @@ -CREATE TABLE ibexa_user_invitations (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INTEGER NOT NULL, used BOOLEAN DEFAULT '0' NOT NULL) +CREATE TABLE ibexa_user_invitations (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(255) NOT NULL, site_access_name VARCHAR(255) NOT NULL, hash VARCHAR(255) NOT NULL, creation_date INTEGER NOT NULL, used BOOLEAN DEFAULT '0' NOT NULL); -- ibexa:sql-statement-separator -CREATE INDEX ibexa_user_invitations_email_idx ON ibexa_user_invitations (email) +CREATE INDEX ibexa_user_invitations_email_idx ON ibexa_user_invitations (email); -- ibexa:sql-statement-separator -CREATE INDEX ibexa_user_invitations_hash_idx ON ibexa_user_invitations (hash) +CREATE INDEX ibexa_user_invitations_hash_idx ON ibexa_user_invitations (hash); -- ibexa:sql-statement-separator -CREATE UNIQUE INDEX ibexa_user_invitations_email_uindex ON ibexa_user_invitations (email) +CREATE UNIQUE INDEX ibexa_user_invitations_email_uindex ON ibexa_user_invitations (email); -- ibexa:sql-statement-separator -CREATE UNIQUE INDEX ibexa_user_invitations_hash_uindex ON ibexa_user_invitations (hash) +CREATE UNIQUE INDEX ibexa_user_invitations_hash_uindex ON ibexa_user_invitations (hash); -- ibexa:sql-statement-separator -CREATE TABLE ibexa_user_invitations_assignments (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, invitation_id INTEGER NOT NULL, user_group_id INTEGER DEFAULT NULL, role_id INTEGER DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE) +CREATE TABLE ibexa_user_invitations_assignments (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, invitation_id INTEGER NOT NULL, user_group_id INTEGER DEFAULT NULL, role_id INTEGER DEFAULT NULL, limitation_type VARCHAR(255) DEFAULT NULL, limitation_value VARCHAR(255) DEFAULT NULL, CONSTRAINT ibexa_user_invitations_assignments_ibexa_user_invitations_id_fk FOREIGN KEY (invitation_id) REFERENCES ibexa_user_invitations (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE); -- ibexa:sql-statement-separator -CREATE INDEX IDX_DA5A7872A35D7AF0 ON ibexa_user_invitations_assignments (invitation_id) +CREATE INDEX IDX_DA5A7872A35D7AF0 ON ibexa_user_invitations_assignments (invitation_id); From f655e65b1cb4473d377bbd0be7905ab3c8b882f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Thu, 23 Jul 2026 16:49:02 +0200 Subject: [PATCH 07/10] IBX-11939: Added schema-presence guards to skip already-applied migrations Checks $schema (already the live, introspected database) before running, and skipIf()s when the tables/columns/FK targets this migration would create already exist -- so installs that built their schema the old way (SchemaBuilderEvent) can adopt Doctrine Migrations without every migration aborting or duplicating existing schema objects. --- src/bundle/Migration/InstallSchemaMigration.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bundle/Migration/InstallSchemaMigration.php b/src/bundle/Migration/InstallSchemaMigration.php index cb953b21..c6af1edb 100644 --- a/src/bundle/Migration/InstallSchemaMigration.php +++ b/src/bundle/Migration/InstallSchemaMigration.php @@ -35,6 +35,8 @@ public function up(Schema $schema): void { $this->abortIfUnsupportedPlatform(SqlPlatform::MYSQL, SqlPlatform::POSTGRESQL, SqlPlatform::SQLITE); + $this->skipIf($schema->hasTable('ibexa_user_invitation'), 'Schema already migrated: table "ibexa_user_invitation" already exists.'); + if ($this->isMySQL()) { $this->addSqlFile(__DIR__ . '/sql/install-schema-mysql.sql'); } elseif ($this->isPostgreSQL()) { From 50a7bfddea75aec339862f18b7d8d87b7d41d426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Thu, 23 Jul 2026 18:36:20 +0200 Subject: [PATCH 08/10] IBX-11939: Recorded schema-guarded migrations as applied, not skipped 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"). --- src/bundle/Migration/InstallSchemaMigration.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bundle/Migration/InstallSchemaMigration.php b/src/bundle/Migration/InstallSchemaMigration.php index c6af1edb..fcae31e0 100644 --- a/src/bundle/Migration/InstallSchemaMigration.php +++ b/src/bundle/Migration/InstallSchemaMigration.php @@ -35,7 +35,9 @@ public function up(Schema $schema): void { $this->abortIfUnsupportedPlatform(SqlPlatform::MYSQL, SqlPlatform::POSTGRESQL, SqlPlatform::SQLITE); - $this->skipIf($schema->hasTable('ibexa_user_invitation'), 'Schema already migrated: table "ibexa_user_invitation" already exists.'); + if ($schema->hasTable('ibexa_user_invitation')) { + return; + } if ($this->isMySQL()) { $this->addSqlFile(__DIR__ . '/sql/install-schema-mysql.sql'); From ba30a4da59fe4d99549bfbf7976ce34637911a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Thu, 23 Jul 2026 19:00:38 +0200 Subject: [PATCH 09/10] IBX-11939: Fixed 4.6 baseline guards to check the branch's own table 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. --- src/bundle/Migration/InstallSchemaMigration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bundle/Migration/InstallSchemaMigration.php b/src/bundle/Migration/InstallSchemaMigration.php index fcae31e0..41bf0346 100644 --- a/src/bundle/Migration/InstallSchemaMigration.php +++ b/src/bundle/Migration/InstallSchemaMigration.php @@ -35,7 +35,7 @@ public function up(Schema $schema): void { $this->abortIfUnsupportedPlatform(SqlPlatform::MYSQL, SqlPlatform::POSTGRESQL, SqlPlatform::SQLITE); - if ($schema->hasTable('ibexa_user_invitation')) { + if ($schema->hasTable('ibexa_user_invitations')) { return; } From b05b64d99b85b1f467df7eb3f318c998ddbd8e9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Mon, 27 Jul 2026 17:24:30 +0200 Subject: [PATCH 10/10] IBX-11939: Removed ibexa/doctrine-migrations VCS repository entry 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. --- composer.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/composer.json b/composer.json index b82aea18..fa53c46a 100644 --- a/composer.json +++ b/composer.json @@ -24,12 +24,6 @@ "EzSystems\\EzPlatformUser\\Tests\\": "tests/lib/" } }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/ibexa/doctrine-migrations.git" - } - ], "require": { "php": "^7.4 || ^8.0", "ibexa/core": "~4.6.0@dev",