diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index 3caa01a002787..e68596f244ed4 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Deprecate `UniqueEntity::getRequiredOptions()` and `UniqueEntity::getDefaultOption()` + * Use a single table named `_schema_subscriber_check` in schema listeners to detect same database connections 7.3 --- diff --git a/src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaListener.php b/src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaListener.php index 6a920ba3c3e7b..50696b4ec2062 100644 --- a/src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaListener.php +++ b/src/Symfony/Bridge/Doctrine/SchemaListener/AbstractSchemaListener.php @@ -12,8 +12,9 @@ namespace Symfony\Bridge\Doctrine\SchemaListener; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Exception\ConnectionException; +use Doctrine\DBAL\Exception\DatabaseObjectExistsException; use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException; -use Doctrine\DBAL\Exception\TableNotFoundException; use Doctrine\DBAL\Schema\Name\Identifier; use Doctrine\DBAL\Schema\Name\UnqualifiedName; use Doctrine\DBAL\Schema\PrimaryKeyConstraint; @@ -29,11 +30,15 @@ protected function getIsSameDatabaseChecker(Connection $connection): \Closure { return static function (\Closure $exec) use ($connection): bool { $schemaManager = method_exists($connection, 'createSchemaManager') ? $connection->createSchemaManager() : $connection->getSchemaManager(); - $checkTable = 'schema_subscriber_check_'.bin2hex(random_bytes(7)); - $table = new Table($checkTable); + $key = bin2hex(random_bytes(7)); + $table = new Table('_schema_subscriber_check'); $table->addColumn('id', Types::INTEGER) ->setAutoincrement(true) ->setNotnull(true); + $table->addColumn('key', Types::STRING) + ->setLength(14) + ->setNotNull(true) + ; if (class_exists(PrimaryKeyConstraint::class)) { $table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true)); @@ -41,20 +46,31 @@ protected function getIsSameDatabaseChecker(Connection $connection): \Closure $table->setPrimaryKey(['id']); } - $schemaManager->createTable($table); + try { + $schemaManager->createTable($table); + } catch (DatabaseObjectExistsException) { + } + + $connection->executeStatement('INSERT INTO _schema_subscriber_check (key) VALUES (:key)', ['key' => $key], ['key' => Types::STRING]); try { - $exec(\sprintf('DROP TABLE %s', $checkTable)); - } catch (\Exception) { - // ignore + $exec('DELETE FROM _schema_subscriber_check WHERE key == :key', ['key' => $key], ['key' => Types::STRING]); + } catch (DatabaseObjectNotFoundException|ConnectionException) { } try { - $schemaManager->dropTable($checkTable); + $rowCount = $connection->executeStatement('DELETE FROM _schema_subscriber_check WHERE key == :key', ['key' => $key], ['key' => Types::STRING]); + + return 0 === $rowCount; + } finally { + [$count] = $connection->executeQuery('SELECT count(id) FROM _schema_subscriber_check')->fetchOne(); - return false; - } catch (DatabaseObjectNotFoundException) { - return true; + if (!$count) { + try { + $schemaManager->dropTable('_schema_subscriber_check'); + } catch (DatabaseObjectNotFoundException) { + } + } } }; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/AbstractSchemaListenerTest.php b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/AbstractSchemaListenerTest.php new file mode 100644 index 0000000000000..7ba3e404e1145 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/SchemaListener/AbstractSchemaListenerTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SchemaListener; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\DriverManager; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Doctrine\SchemaListener\AbstractSchemaListener; + +#[RequiresPhpExtension('pdo_sqlite')] +class AbstractSchemaListenerTest extends TestCase +{ + public function testSameDatabaseChecker() + { + $connectionParams = [ + 'dbname' => ':memory:', + 'driver' => 'pdo_sqlite', + ]; + // Create two distinct in-memory SQLite databases + $connection1 = DriverManager::getConnection($connectionParams); + $connection2 = DriverManager::getConnection($connectionParams); + + self::assertTrue($this->getIsSameDatabaseChecker($connection1)($connection1->executeStatement(...))); + self::assertFalse($this->getIsSameDatabaseChecker($connection1)($connection2->executeStatement(...))); + + $remainingTables = $connection1->executeQuery('SELECT name FROM sqlite_schema WHERE name <> "sqlite_sequence"')->fetchFirstColumn(); + self::assertSame([], $remainingTables, 'Temporary table was dropped'); + } + + private function getIsSameDatabaseChecker(Connection $connection): \Closure + { + return (new class extends AbstractSchemaListener { + public function postGenerateSchema($event): void + { + } + + public function getIsSameDatabaseChecker(Connection $connection): \Closure + { + return parent::getIsSameDatabaseChecker($connection); + } + })->getIsSameDatabaseChecker($connection); + } +}