From a1c2be140dd8277e160f89004eb7ca80b7ec0a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Sat, 14 Jun 2025 13:50:33 +0200 Subject: [PATCH 1/5] Update branch metadata - 3.5.x has been created - 3.4.0 has been released - 3.3.x is no longer maintained --- .doctrine-project.json | 10 ++++++++-- README.md | 14 +++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.doctrine-project.json b/.doctrine-project.json index 8dfa2ed2450..9ef52ee407e 100644 --- a/.doctrine-project.json +++ b/.doctrine-project.json @@ -11,17 +11,23 @@ "slug": "latest", "upcoming": true }, + { + "name": "3.5", + "branchName": "3.5.x", + "slug": "3.5", + "upcoming": true + }, { "name": "3.4", "branchName": "3.4.x", "slug": "3.4", - "upcoming": true + "current": true }, { "name": "3.3", "branchName": "3.3.x", "slug": "3.3", - "current": true + "maintained": false }, { "name": "3.2", diff --git a/README.md b/README.md index 47a36ddb27e..f3334667d05 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -| [4.0.x][4.0] | [3.4.x][3.4] | [3.3.x][3.3] | [2.21.x][2.21] | [2.20.x][2.20] | +| [4.0.x][4.0] | [3.5.x][3.5] | [3.4.x][3.4] | [2.21.x][2.21] | [2.20.x][2.20] | |:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:| -| [![Build status][4.0 image]][4.0] | [![Build status][3.4 image]][3.4] | [![Build status][3.3 image]][3.3] | [![Build status][2.21 image]][2.21] | [![Build status][2.20 image]][2.20] | -| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.4 coverage image]][3.4 coverage] | [![Coverage Status][3.3 coverage image]][3.3 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] | +| [![Build status][4.0 image]][4.0] | [![Build status][3.5 image]][3.5] | [![Build status][3.4 image]][3.4] | [![Build status][2.21 image]][2.21] | [![Build status][2.20 image]][2.20] | +| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.5 coverage image]][3.5 coverage] | [![Coverage Status][3.4 coverage image]][3.4 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] | Doctrine ORM is an object-relational mapper for PHP 8.1+ that provides transparent persistence for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features @@ -20,14 +20,14 @@ without requiring unnecessary code duplication. [4.0]: https://github.com/doctrine/orm/tree/4.0.x [4.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/4.0.x/graph/badge.svg [4.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/4.0.x + [3.5 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.5.x + [3.5]: https://github.com/doctrine/orm/tree/3.5.x + [3.5 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.5.x/graph/badge.svg + [3.5 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.5.x [3.4 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.4.x [3.4]: https://github.com/doctrine/orm/tree/3.4.x [3.4 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.4.x/graph/badge.svg [3.4 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.4.x - [3.3 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.3.x - [3.3]: https://github.com/doctrine/orm/tree/3.3.x - [3.3 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.3.x/graph/badge.svg - [3.3 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.3.x [2.21 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.21.x [2.21]: https://github.com/doctrine/orm/tree/2.21.x [2.21 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.21.x/graph/badge.svg From ab11244f08be6f6d9dbea52a7ebf5316a8b8f8e6 Mon Sep 17 00:00:00 2001 From: eltharin Date: Mon, 16 Jun 2025 08:03:47 +0200 Subject: [PATCH 2/5] repair code block bad showing --- docs/en/reference/dql-doctrine-query-language.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/reference/dql-doctrine-query-language.rst b/docs/en/reference/dql-doctrine-query-language.rst index 713f53dff88..127cbc4e2a4 100644 --- a/docs/en/reference/dql-doctrine-query-language.rst +++ b/docs/en/reference/dql-doctrine-query-language.rst @@ -685,6 +685,7 @@ You can hydrate an entity nested in a DTO : // CustomerDTO => {name : 'DOE', email: null, address : {city: 'New York', zip: '10011', address: 'Abbey Road'} In a Dto, if you want add all fields of an entity, you can use ``.*`` : + .. code-block:: php Date: Tue, 17 Jun 2025 08:35:16 +0200 Subject: [PATCH 3/5] Use the correct environment variable name for lazy objects The test suite checks for ENABLE_NATIVE_LAZY_OBJECTS I have also renamed the matrix variable for the sake of consistency. --- .github/workflows/continuous-integration.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 78ac2c4bcc8..284e373319f 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -43,27 +43,27 @@ jobs: - "pdo_sqlite" deps: - "highest" - lazy_proxy: + native_lazy: - "0" include: - php-version: "8.2" dbal-version: "4@dev" extension: "pdo_sqlite" - lazy_proxy: "0" + native_lazy: "0" - php-version: "8.2" dbal-version: "4@dev" extension: "sqlite3" - lazy_proxy: "0" + native_lazy: "0" - php-version: "8.1" dbal-version: "default" deps: "lowest" extension: "pdo_sqlite" - lazy_proxy: "0" + native_lazy: "0" - php-version: "8.4" dbal-version: "default" deps: "highest" extension: "pdo_sqlite" - lazy_proxy: "1" + native_lazy: "1" steps: - name: "Checkout" @@ -93,18 +93,18 @@ jobs: run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml" env: ENABLE_SECOND_LEVEL_CACHE: 0 - ENABLE_LAZY_PROXY: ${{ matrix.lazy_proxy }} + ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }} - name: "Run PHPUnit with Second Level Cache" run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml" env: ENABLE_SECOND_LEVEL_CACHE: 1 - ENABLE_LAZY_PROXY: ${{ matrix.lazy_proxy }} + ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }} - name: "Upload coverage file" uses: "actions/upload-artifact@v4" with: - name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.lazy_proxy }}-coverage" + name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.native_lazy }}-coverage" path: "coverage*.xml" From 4e5e3c5e5046cd21108fb66c6cb62640a6f532da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Tue, 17 Jun 2025 20:47:03 +0200 Subject: [PATCH 4/5] Enable native lazy objects by default This should make the test suite look less like a christmas tree. --- src/UnitOfWork.php | 2 +- .../Tests/ORM/Functional/PropertyHooksTest.php | 5 +++++ .../Tests/ORM/Hydration/ObjectHydratorTest.php | 5 ++--- tests/Tests/ORM/Proxy/ProxyFactoryTest.php | 12 ++++++++++++ tests/Tests/ORM/UnitOfWorkTest.php | 16 +++++++++++++++- tests/Tests/OrmFunctionalTestCase.php | 7 +++++++ tests/Tests/TestUtil.php | 17 +++++++++++++++++ 7 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/UnitOfWork.php b/src/UnitOfWork.php index a687e1a26a6..949bf20dd47 100644 --- a/src/UnitOfWork.php +++ b/src/UnitOfWork.php @@ -3057,7 +3057,7 @@ public function initializeObject(object $obj): void */ public function isUninitializedObject(mixed $obj): bool { - if ($this->em->getConfiguration()->isNativeLazyObjectsEnabled() && ! ($obj instanceof Collection)) { + if ($this->em->getConfiguration()->isNativeLazyObjectsEnabled() && ! ($obj instanceof Collection) && is_object($obj)) { return $this->em->getClassMetadata($obj::class)->reflClass->isUninitializedLazyObject($obj); } diff --git a/tests/Tests/ORM/Functional/PropertyHooksTest.php b/tests/Tests/ORM/Functional/PropertyHooksTest.php index a53af594810..ac3a3e1f3b4 100644 --- a/tests/Tests/ORM/Functional/PropertyHooksTest.php +++ b/tests/Tests/ORM/Functional/PropertyHooksTest.php @@ -4,6 +4,7 @@ namespace Doctrine\Tests\ORM\Functional; +use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\ORM\Mapping\MappingException; use Doctrine\Tests\Models\PropertyHooks\MappingVirtualProperty; use Doctrine\Tests\Models\PropertyHooks\User; @@ -17,6 +18,10 @@ protected function setUp(): void { parent::setUp(); + if ($this->_em->getConnection()->getDatabasePlatform() instanceof AbstractMySQLPlatform) { + self::markTestSkipped('MySQL/MariaDB is case-insensitive by default, and the logic of this test relies on case sensitivity.'); + } + if (! $this->_em->getConfiguration()->isNativeLazyObjectsEnabled()) { $this->markTestSkipped('Property hooks require native lazy objects to be enabled.'); } diff --git a/tests/Tests/ORM/Hydration/ObjectHydratorTest.php b/tests/Tests/ORM/Hydration/ObjectHydratorTest.php index 526817263a7..cbfac5fa6b9 100644 --- a/tests/Tests/ORM/Hydration/ObjectHydratorTest.php +++ b/tests/Tests/ORM/Hydration/ObjectHydratorTest.php @@ -9,7 +9,6 @@ use Doctrine\ORM\Internal\Hydration\ObjectHydrator; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; -use Doctrine\ORM\Proxy\InternalProxy; use Doctrine\ORM\Proxy\ProxyFactory; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\Tests\Mocks\ArrayResultFactory; @@ -1030,7 +1029,7 @@ public function testCreatesProxyForLazyLoadingWithForeignKeys(): void 'Proxies', ProxyFactory::AUTOGENERATE_ALWAYS, ) extends ProxyFactory { - public function getProxy(string $className, array $identifier): InternalProxy + public function getProxy(string $className, array $identifier): object { TestCase::assertSame(ECommerceShipping::class, $className); TestCase::assertSame(['id' => 42], $identifier); @@ -1084,7 +1083,7 @@ public function testCreatesProxyForLazyLoadingWithForeignKeysWithAliasedProductE 'Proxies', ProxyFactory::AUTOGENERATE_ALWAYS, ) extends ProxyFactory { - public function getProxy(string $className, array $identifier): InternalProxy + public function getProxy(string $className, array $identifier): object { TestCase::assertSame(ECommerceShipping::class, $className); TestCase::assertSame(['id' => 42], $identifier); diff --git a/tests/Tests/ORM/Proxy/ProxyFactoryTest.php b/tests/Tests/ORM/Proxy/ProxyFactoryTest.php index 05b19a4abcf..edd73e58598 100644 --- a/tests/Tests/ORM/Proxy/ProxyFactoryTest.php +++ b/tests/Tests/ORM/Proxy/ProxyFactoryTest.php @@ -120,6 +120,10 @@ public function testSkipAbstractClassesOnGeneration(): void #[Group('DDC-2432')] public function testFailedProxyLoadingDoesNotMarkTheProxyAsInitialized(): void { + if ($this->emMock->getConfiguration()->isNativeLazyObjectsEnabled()) { + self::markTestSkipped('This test is not relevant when native lazy objects are enabled'); + } + $persister = $this->getMockBuilder(BasicEntityPersister::class) ->onlyMethods(['load']) ->disableOriginalConstructor() @@ -146,6 +150,10 @@ public function testFailedProxyLoadingDoesNotMarkTheProxyAsInitialized(): void #[Group('DDC-2432')] public function testFailedProxyCloningDoesNotMarkTheProxyAsInitialized(): void { + if ($this->emMock->getConfiguration()->isNativeLazyObjectsEnabled()) { + self::markTestSkipped('This test is not relevant when native lazy objects are enabled'); + } + $persister = $this->getMockBuilder(BasicEntityPersister::class) ->onlyMethods(['load', 'getClassMetadata']) ->disableOriginalConstructor() @@ -172,6 +180,10 @@ public function testFailedProxyCloningDoesNotMarkTheProxyAsInitialized(): void public function testProxyClonesParentFields(): void { + if ($this->emMock->getConfiguration()->isNativeLazyObjectsEnabled()) { + self::markTestSkipped('This test is not relevant when native lazy objects are enabled'); + } + $companyEmployee = new CompanyEmployee(); $companyEmployee->setSalary(1000); // A property on the CompanyEmployee $companyEmployee->setName('Bob'); // A property on the parent class, CompanyPerson diff --git a/tests/Tests/ORM/UnitOfWorkTest.php b/tests/Tests/ORM/UnitOfWorkTest.php index 7faad81bfa1..e9e47803a55 100644 --- a/tests/Tests/ORM/UnitOfWorkTest.php +++ b/tests/Tests/ORM/UnitOfWorkTest.php @@ -20,6 +20,7 @@ use Doctrine\ORM\Mapping\GeneratedValue; use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\ManyToOne; +use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\Mapping\Version; use Doctrine\ORM\OptimisticLockException; use Doctrine\ORM\ORMInvalidArgumentException; @@ -40,6 +41,7 @@ use stdClass; use function enum_exists; +use function is_object; use function random_int; use function uniqid; @@ -278,7 +280,19 @@ public function testRejectsChangeSetComputationForObjectsWithInvalidAssociationV $user->username = 'John'; $user->avatar = $invalidValue; - $this->expectException(ORMInvalidArgumentException::class); + if ( + is_object($invalidValue) && + ! $invalidValue instanceof ArrayCollection && + $this->_emMock->getConfiguration()->isNativeLazyObjectsEnabled() + ) { + // in the case of stdClass, the changeset is rejected because + // stdClass is not a valid entity + // when using native lazy objects, this happens because UnitOfWork::isUninitializedObject() + // needs to load the class metadata to do its job + $this->expectException(MappingException::class); + } else { + $this->expectException(ORMInvalidArgumentException::class); + } $this->_unitOfWork->computeChangeSet($metadata, $user); } diff --git a/tests/Tests/OrmFunctionalTestCase.php b/tests/Tests/OrmFunctionalTestCase.php index 644f58b799e..67a42d30909 100644 --- a/tests/Tests/OrmFunctionalTestCase.php +++ b/tests/Tests/OrmFunctionalTestCase.php @@ -941,6 +941,13 @@ protected function getEntityManager( $enableNativeLazyObjects = getenv('ENABLE_NATIVE_LAZY_OBJECTS'); + if ($enableNativeLazyObjects === false) { + // If the environment variable is not set, we default to true. + // This is OK because environment variables are always strings, and + // we are comparing it to a boolean. + $enableNativeLazyObjects = true; + } + if (PHP_VERSION_ID >= 80400 && $enableNativeLazyObjects) { $config->enableNativeLazyObjects(true); } diff --git a/tests/Tests/TestUtil.php b/tests/Tests/TestUtil.php index 33869c3b174..01f81b610e3 100644 --- a/tests/Tests/TestUtil.php +++ b/tests/Tests/TestUtil.php @@ -19,12 +19,14 @@ use function explode; use function fwrite; use function get_debug_type; +use function getenv; use function in_array; use function sprintf; use function str_starts_with; use function strlen; use function substr; +use const PHP_VERSION_ID; use const STDERR; /** @@ -89,8 +91,23 @@ public static function getPrivilegedConnection(): DbalExtensions\Connection public static function configureProxies(Configuration $configuration): void { + $enableNativeLazyObjects = getenv('ENABLE_NATIVE_LAZY_OBJECTS'); + + if ($enableNativeLazyObjects === false) { + // If the environment variable is not set, we default to true. + // This is OK because environment variables are always strings, and + // we are comparing it to a boolean. + $enableNativeLazyObjects = true; + } + $configuration->setProxyDir(__DIR__ . '/Proxies'); $configuration->setProxyNamespace('Doctrine\Tests\Proxies'); + + if (PHP_VERSION_ID >= 80400 && $enableNativeLazyObjects) { + $configuration->enableNativeLazyObjects(true); + + return; + } } private static function initializeDatabase(): void From f2c902ee03322223a95f315728b53b6316d27e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Tue, 17 Jun 2025 23:35:47 +0200 Subject: [PATCH 5/5] Rewrite test with native lazy ghost I do not think this needs to be tested on all versions of PHP, using native lazy objects allows us to remove a deprecation. --- tests/Tests/ORM/EntityManagerTest.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/Tests/ORM/EntityManagerTest.php b/tests/Tests/ORM/EntityManagerTest.php index e724d9ac632..23ee89d347c 100644 --- a/tests/Tests/ORM/EntityManagerTest.php +++ b/tests/Tests/ORM/EntityManagerTest.php @@ -25,9 +25,10 @@ use PHPUnit\Framework\Assert; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RequiresPhp; +use ReflectionClass; use ReflectionProperty; use stdClass; -use Symfony\Component\VarExporter\LazyGhostTrait; use TypeError; class EntityManagerTest extends OrmTestCase @@ -180,17 +181,12 @@ public function testWrapInTransactionReThrowsThrowables(): void } /** Resetting the EntityManager relies on lazy objects until https://github.com/doctrine/orm/issues/5933 is resolved */ + #[RequiresPhp('8.4')] public function testLazyGhostEntityManager(): void { - $em = new class () extends EntityManager { - use LazyGhostTrait; + $reflector = new ReflectionClass(EntityManager::class); - public function __construct() - { - } - }; - - $em = $em::createLazyGhost(static function ($em): void { + $em = $reflector->newLazyGhost($initializer = static function (EntityManager $em): void { $r = new ReflectionProperty(EntityManager::class, 'unitOfWork'); $r->setValue($em, new class () extends UnitOfWork { public function __construct() @@ -207,7 +203,7 @@ public function clear(): void $em->close(); $this->assertFalse($em->isOpen()); - $em->resetLazyObject(); + $reflector->resetAsLazyGhost($em, $initializer); $this->assertTrue($em->isOpen()); }