From 5a2fd788e307422df5936494dac8ed7209ce56c4 Mon Sep 17 00:00:00 2001 From: W0rma Date: Wed, 16 Sep 2026 16:02:31 +0200 Subject: [PATCH 1/5] Bump phpstan version to 2.2 --- composer.json | 2 +- phpstan.neon | 8 ++++++++ src/Codeception/Module/Doctrine.php | 4 +++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index ccdaeae..a28bbd8 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "doctrine/annotations": "^2.0.1", "doctrine/data-fixtures": "^1.6", "doctrine/orm": "^2.14 || ^3.0", - "phpstan/phpstan": "^1.10.58", + "phpstan/phpstan": "^2.2", "symfony/cache": "^5.4.35 || ^6.4.3 || ^7.0", "symfony/doctrine-bridge": "^5.4.35 || ^6.4.3 || ^7.0", "symfony/uid": "^5.4.35 || ^6.4.3 || ^7.0" diff --git a/phpstan.neon b/phpstan.neon index f0dad3e..8f11761 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,6 +9,7 @@ parameters: - tests/data/doctrine_fixtures/TestFixture2.php - tests/_support/UnitTester.php reportUnmatchedIgnoredErrors: true + treatPhpDocTypesAsCertain: false ignoreErrors: - path: tests/ message: '#Property \S+ is never read, only written#' @@ -20,4 +21,11 @@ parameters: message: '#Method \S+ has no return type specified#' - path: tests/ message: '#(?:Method|Property) .+ with generic (?:interface|class) \S+ does not specify its types#' + - path: tests/ + identifier: property.unusedType - identifier: missingType.iterableValue + - path: tests/unit/Codeception/Module/Doctrine2Test.php + identifier: argument.type + message: '#Parameter \#1 \$orderings of method Doctrine\\Common\\Collections\\Criteria::orderBy\(\) expects#' + count: 2 + reportUnmatched: false diff --git a/src/Codeception/Module/Doctrine.php b/src/Codeception/Module/Doctrine.php index 9d5a013..7d7e979 100644 --- a/src/Codeception/Module/Doctrine.php +++ b/src/Codeception/Module/Doctrine.php @@ -263,6 +263,7 @@ protected function retrieveEntityManager(): void } $connection = $this->em->getConnection(); + // @phpstan-ignore-next-line if (method_exists($connection, 'getNativeConnection')) { $connection->getNativeConnection(); } else { @@ -732,7 +733,7 @@ public function loadFixtures($fixtures, bool $append = true): void try { $fixtureInstance = new $fixture(); - } catch (Exception $exception) { // @phpstan-ignore-line https://github.com/phpstan/phpstan/issues/6574 + } catch (Exception $exception) { throw new ModuleException( __CLASS__, sprintf( @@ -991,6 +992,7 @@ protected function _buildAssociationQuery(QueryBuilder $qb, string $assoc, strin { $data = $this->em->getClassMetadata($assoc); foreach ($params as $key => $val) { + // @phpstan-ignore-next-line if ($data->associationMappings !== null && array_key_exists($key, $data->associationMappings)) { $map = $data->associationMappings[$key]; if (is_array($val)) { From f953a9b3049194868ad5bddefd3bd9a47c405f33 Mon Sep 17 00:00:00 2001 From: W0rma Date: Wed, 16 Sep 2026 15:56:32 +0200 Subject: [PATCH 2/5] Remove superfluous type check Reported by phpstan --- src/Codeception/Module/Doctrine.php | 8 -------- src/Codeception/Util/ReflectionPropertyAccessor.php | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Codeception/Module/Doctrine.php b/src/Codeception/Module/Doctrine.php index 7d7e979..862dd4b 100644 --- a/src/Codeception/Module/Doctrine.php +++ b/src/Codeception/Module/Doctrine.php @@ -254,14 +254,6 @@ protected function retrieveEntityManager(): void } - if (!($this->em instanceof EntityManagerInterface)) { - throw new ModuleConfigException( - __CLASS__, - "Connection object is not an instance of \\Doctrine\\ORM\\EntityManagerInterface.\n" - . "Use `connection_callback` or dependent framework modules to specify one" - ); - } - $connection = $this->em->getConnection(); // @phpstan-ignore-next-line if (method_exists($connection, 'getNativeConnection')) { diff --git a/src/Codeception/Util/ReflectionPropertyAccessor.php b/src/Codeception/Util/ReflectionPropertyAccessor.php index 3882292..4fbf063 100644 --- a/src/Codeception/Util/ReflectionPropertyAccessor.php +++ b/src/Codeception/Util/ReflectionPropertyAccessor.php @@ -72,7 +72,7 @@ private function setPropertiesForClass(?object $obj, string $class, array $data) */ public function setProperties(?object $obj, array $data): void { - if (!$obj || !is_object($obj)) { + if (!is_object($obj)) { throw new InvalidArgumentException('Cannot set properties for "' . gettype($obj) . '", expecting object'); } $class = get_class($obj); From 135b5fae7c50fa35ebf15be05d2074674972ed93 Mon Sep 17 00:00:00 2001 From: W0rma Date: Mon, 2 Nov 2026 12:34:11 +0100 Subject: [PATCH 3/5] Bump doctrine/data-fixtures version to fix the --prefer-lowest ci job from 1.7.0, the listener is declared as postPersist(PostPersistEventArgs $args), which works with ORM 2.14+ and ORM 3. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a28bbd8..9bedb20 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "require-dev": { "codeception/stub": "^4.1.3", "doctrine/annotations": "^2.0.1", - "doctrine/data-fixtures": "^1.6", + "doctrine/data-fixtures": "^1.7", "doctrine/orm": "^2.14 || ^3.0", "phpstan/phpstan": "^2.2", "symfony/cache": "^5.4.35 || ^6.4.3 || ^7.0", From a36e69e1395f2c0104d6cba8f05e4feaf499584a Mon Sep 17 00:00:00 2001 From: W0rma Date: Wed, 16 Sep 2026 15:05:27 +0200 Subject: [PATCH 4/5] Fix support for doctrine/collections v3 --- phpstan.neon | 5 ---- src/Codeception/Module/Doctrine.php | 4 ++- .../unit/Codeception/Module/Doctrine2Test.php | 29 +++++++++++++++---- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 8f11761..8e5cec5 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -24,8 +24,3 @@ parameters: - path: tests/ identifier: property.unusedType - identifier: missingType.iterableValue - - path: tests/unit/Codeception/Module/Doctrine2Test.php - identifier: argument.type - message: '#Parameter \#1 \$orderings of method Doctrine\\Common\\Collections\\Criteria::orderBy\(\) expects#' - count: 2 - reportUnmatched: false diff --git a/src/Codeception/Module/Doctrine.php b/src/Codeception/Module/Doctrine.php index 862dd4b..4e879d1 100644 --- a/src/Codeception/Module/Doctrine.php +++ b/src/Codeception/Module/Doctrine.php @@ -999,7 +999,9 @@ protected function _buildAssociationQuery(QueryBuilder $qb, string $assoc, strin } elseif ($val instanceof Criteria) { $qb->addCriteria($val); } elseif ($val instanceof Expression) { - $qb->addCriteria(Criteria::create(true)->where($val)); + // Doctrine Collections 2.x support + $criteria = defined(Criteria::class . '::ASC') ? Criteria::create(true) : Criteria::create(); + $qb->addCriteria($criteria->where($val)); } else { $qb->andWhere(sprintf('%s.%s = ?%s', $alias, $key, $paramIndex)); $qb->setParameter($paramIndex, $val); diff --git a/tests/unit/Codeception/Module/Doctrine2Test.php b/tests/unit/Codeception/Module/Doctrine2Test.php index 216038d..74cfe48 100644 --- a/tests/unit/Codeception/Module/Doctrine2Test.php +++ b/tests/unit/Codeception/Module/Doctrine2Test.php @@ -9,6 +9,7 @@ use Codeception\Stub; use Codeception\Test\Unit; use Doctrine\Common\Collections\Criteria; +use Doctrine\Common\Collections\Order; use Doctrine\Common\DataFixtures\Executor\ORMExecutor; use Doctrine\Common\DataFixtures\Loader; use Doctrine\Common\DataFixtures\Purger\ORMPurger; @@ -258,17 +259,17 @@ public function testCriteria() { $this->module->haveInRepository(PlainEntity::class, ['name' => 'Test 1']); $this->module->seeInRepository(PlainEntity::class, [ - Criteria::create(true)->where( + self::createCriteria()->where( Criteria::expr()->eq('name', 'Test 1') ), ]); $this->module->seeInRepository(PlainEntity::class, [ - Criteria::create(true)->where( + self::createCriteria()->where( Criteria::expr()->contains('name', 'est') ), ]); $this->module->seeInRepository(PlainEntity::class, [ - Criteria::create(true)->where( + self::createCriteria()->where( Criteria::expr()->in('name', ['Test 1']) ), ]); @@ -296,6 +297,18 @@ public function testOrderBy() $getName = fn($entity) => $entity->getName(); + $collectionsVersion = InstalledVersions::getVersion('doctrine/collections'); + if (version_compare($collectionsVersion, '3.0', '<')) { + $ascending = 'asc'; + $descending = 'desc'; + } elseif (version_compare($collectionsVersion, '3.1', '<')) { + $ascending = Order::Ascending; + $descending = Order::Descending; + } else { + $ascending = \SortDirection::Ascending; + $descending = \SortDirection::Descending; + } + $this->assertSame( [ 'a', @@ -303,7 +316,7 @@ public function testOrderBy() 'c', ], array_map($getName, $this->module->grabEntitiesFromRepository(PlainEntity::class, [ - Criteria::create(true)->orderBy(['name' => 'asc']), + self::createCriteria()->orderBy(['name' => $ascending]), ])) ); @@ -314,7 +327,7 @@ public function testOrderBy() 'a', ], array_map($getName, $this->module->grabEntitiesFromRepository(PlainEntity::class, [ - Criteria::create(true)->orderBy(['name' => 'desc']), + self::createCriteria()->orderBy(['name' => $descending]), ])) ); } @@ -597,4 +610,10 @@ public function testHaveFakeRepository() $this->assertContains($e1, $entities); $this->assertContains($e2, $entities); } + + private static function createCriteria(): Criteria + { + // Doctrine Collections 2.x support + return defined(Criteria::class . '::ASC') ? Criteria::create(true) : Criteria::create(); + } } From 6e75d7964546d31113c59934eb4220c194a9376d Mon Sep 17 00:00:00 2001 From: W0rma Date: Mon, 2 Nov 2026 13:10:28 +0100 Subject: [PATCH 5/5] Always use pdo_sqlite driver This should fix random ci errors which occur when the ULID's random bytes include a 0x00. The failures come from a known limitation of PHP's sqlite3 extension, not from the module. The stored id comes back truncated and can't be converted to a Ulid again. --- tests/unit/Codeception/Module/Doctrine2Test.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/unit/Codeception/Module/Doctrine2Test.php b/tests/unit/Codeception/Module/Doctrine2Test.php index 74cfe48..2846a4f 100644 --- a/tests/unit/Codeception/Module/Doctrine2Test.php +++ b/tests/unit/Codeception/Module/Doctrine2Test.php @@ -72,14 +72,7 @@ protected function _setUp() require_once $dir . "/CircularRelations/C.php"; require_once $dir . '/EntityWithUlid.php'; - $sqliteDriver = 'sqlite3'; - // The driver "sqlite3" is only available as-of doctrine/dbal:3.5 - // Use "pdo_sqlite" for older versions - if (version_compare(InstalledVersions::getVersion('doctrine/dbal'), '3.5', '<')) { - $sqliteDriver = 'pdo_sqlite'; - } - - $connection = DriverManager::getConnection(['driver' => $sqliteDriver, 'memory' => true]); + $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'memory' => true]); if (version_compare(InstalledVersions::getVersion('doctrine/orm'), '3', '>=')) { $configuration = ORMSetup::createAttributeMetadataConfiguration([$dir], true);