From adba91a951687576497776884cebc64417dba5a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 29 Oct 2025 12:25:51 +0100 Subject: [PATCH 1/5] Enable native lazy objects by default, add an option to disable it --- config/schema/mongodb-1.0.xsd | 2 + docs/config.rst | 45 +++++++++++++++++++ src/DependencyInjection/Configuration.php | 29 +++++++++++- .../DoctrineMongoDBExtension.php | 19 ++++---- .../DependencyInjection/ConfigurationTest.php | 6 ++- .../Fixtures/config/xml/full.xml | 2 + .../Fixtures/config/yml/full.yml | 2 + 7 files changed, 94 insertions(+), 11 deletions(-) diff --git a/config/schema/mongodb-1.0.xsd b/config/schema/mongodb-1.0.xsd index 90588f99..20344649 100644 --- a/config/schema/mongodb-1.0.xsd +++ b/config/schema/mongodb-1.0.xsd @@ -17,6 +17,8 @@ + + diff --git a/docs/config.rst b/docs/config.rst index 35312f1c..84374da5 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -629,6 +629,47 @@ Using Queryable Encryption For details on configuring Queryable Encryption (QE) and Client-Side Field-Level Encryption (CSFLE), see :doc:`encryption`. +Lazy object implementation +-------------------------- + +Doctrine MongoDB ODM uses lazy objects for lazy instantiation of references. +The original implementation is based on the `ProxyManager` library. +Since version 2.10 of Doctrine MongoDB ODM, support for Symfony lazy ghost +objects has been added. And in version 2.14, support for PHP 7.4 native lazy +objects has been added. + +The bundle select the best available lazy object implementation based on the +installed packages and PHP version. You can override this behavior by setting +the following configuration options to enable or disable specific lazy object +implementations. This is not recommended unless you have a specific reason to do +so. Please open an issue if the default Native Lazy Objects are not working as +expected. + +- ``enable_native_lazy_objects`` is ``true`` by default when PHP 8.4+ and ``doctrine/mongodb-odm`` 2.14+ are installed. + When enabled, native lazy objects will be used for lazy loading references. +- ``enable_lazy_ghost_objects`` is ``true`` by default when ``doctrine/mongodb-odm`` 2.10+ is installed. + When enabled, Symfony lazy ghost objects will be used for lazy loading references. + This option is ignored if ``enable_native_lazy_objects`` is ``true``. +- When both options are ``false``, the original ``ProxyManager`` based lazy objects will be used. + +.. configuration-block:: + + .. code-block:: yaml + + doctrine_mongodb: + enable_native_lazy_objects: false + enable_lazy_ghost_objects: false + + + .. code-block:: php + + use Symfony\Config\DoctrineMongodbConfig; + + return static function (DoctrineMongodbConfig $config): void { + $config->enableNativeLazyObjects(false); + $config->enableLazyGhostObjects(false); + + Full Default Configuration -------------------------- @@ -739,6 +780,8 @@ Full Default Configuration default_document_manager: ~ default_connection: ~ default_database: default + enable_native_lazy_objects: true # Enabled by default if PHP 8.4+ and doctrine/mongodb-odm 2.14+ are installed + enable_lazy_ghost_objects: true # Enabled by default if doctrine/mongodb-odm 2.10+ is installed .. code-block:: xml @@ -814,6 +857,8 @@ Full Default Configuration return static function (DoctrineMongodbConfig $config): void { $config->autoGenerateHydratorClasses(0); $config->autoGenerateProxyClasses(0); + $config->enableNativeLazyObjects(true); // Enabled by default if PHP 8.4+ and doctrine/mongodb-odm 2.14+ are installed + $config->enableLazyGhostObjects(true); // Enabled by default if doctrine/mongodb-odm 2.10+ is installed $config->defaultConnection(''); $config->defaultDatabase('default'); $config->defaultDocumentManager(''); diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index c4fec903..bdcca2c8 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -7,6 +7,7 @@ use Doctrine\ODM\MongoDB\Configuration as ODMConfiguration; use Doctrine\ODM\MongoDB\Repository\DefaultGridFSRepository; use Doctrine\ODM\MongoDB\Repository\DocumentRepository; +use InvalidArgumentException; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -20,6 +21,7 @@ use function preg_match; use const JSON_THROW_ON_ERROR; +use const PHP_VERSION_ID; /** * FrameworkExtension configuration structure. @@ -43,11 +45,34 @@ public function getConfigTreeBuilder(): TreeBuilder ->children() ->scalarNode('proxy_namespace')->defaultValue('MongoDBODMProxies')->end() ->scalarNode('proxy_dir')->defaultValue('%kernel.cache_dir%/doctrine/odm/mongodb/Proxies')->end() + ->booleanNode('enable_native_lazy_objects') + ->defaultValue(PHP_VERSION_ID >= 80400 && method_exists(ODMConfiguration::class, 'setUseNativeLazyObject')) + ->info('Requires PHP 8.4+ and doctrine/mongodb-odm 2.14+') + ->setDeprecated('doctrine/mongodb-odm-bundle', '5.4', 'The "%node%" option is deprecated and will be removed in 6.0. Native Lazy Objects are enable by default when using PHP 8.4+ and doctrine/mongodb-odm 2.14+.') + ->validate() + ->ifTrue() + ->then(static function (): void { + if (PHP_VERSION_ID < 80400) { + throw new InvalidArgumentException('Native lazy objects require PHP 8.4 or higher.'); + } + + if (! method_exists(ODMConfiguration::class, 'setUseNativeLazyObject')) { + throw new InvalidArgumentException('Native lazy objects require doctrine/mongodb-odm 2.14 or higher.'); + } + }) + ->end() + ->end() ->booleanNode('enable_lazy_ghost_objects') ->defaultValue(method_exists(ODMConfiguration::class, 'setUseLazyGhostObject')) + ->info('Requires doctrine/mongodb-odm 2.12+') + ->setDeprecated('doctrine/mongodb-odm-bundle', '5.4', 'The "%node%" option is deprecated and will be removed in 6.0. Native Lazy Objects are enable by default when using PHP 8.4+ and doctrine/mongodb-odm 2.14+.') ->validate() - ->ifTrue(static fn ($v) => $v === true && ! method_exists(ODMConfiguration::class, 'setUseLazyGhostObject')) - ->thenInvalid('Lazy ghost objects require doctrine/mongodb-odm 2.10 or higher.') + ->ifTrue() + ->then(static function (): void { + if (! method_exists(ODMConfiguration::class, 'setUseLazyGhostObject')) { + throw new InvalidArgumentException('Lazy ghost objects require doctrine/mongodb-odm 2.10 or higher.'); + } + }) ->end() ->end() ->scalarNode('auto_generate_proxy_classes') diff --git a/src/DependencyInjection/DoctrineMongoDBExtension.php b/src/DependencyInjection/DoctrineMongoDBExtension.php index b4b23847..32967b5f 100644 --- a/src/DependencyInjection/DoctrineMongoDBExtension.php +++ b/src/DependencyInjection/DoctrineMongoDBExtension.php @@ -486,9 +486,8 @@ public function load(array $configs, ContainerBuilder $container): void $container->removeDefinition('doctrine_mongodb.odm.command.load_data_fixtures'); } - // Requires doctrine/mongodb-odm 2.10 $container->getDefinition('doctrine_mongodb') - ->setArgument(5, $config['enable_lazy_ghost_objects'] ? Proxy::class : LazyLoadingInterface::class); + ->setArgument(5, $config['enable_lazy_ghost_objects'] ? LazyLoadingInterface::class : Proxy::class); // load the connections $this->loadConnections($config['connections'], $container, $config); @@ -501,7 +500,11 @@ public function load(array $configs, ContainerBuilder $container): void $config['default_document_manager'], $config['default_database'], $container, - $config['enable_lazy_ghost_objects'], + match (true) { + $config['enable_native_lazy_objects'] => 'setUseNativeLazyObject', + $config['enable_lazy_ghost_objects'] => 'setUseLazyGhostObject', + default => null, + }, $config['connections'], ); @@ -599,7 +602,7 @@ protected function overrideParameters(array $options, ContainerBuilder $containe * @param ContainerBuilder $container A ContainerBuilder instance * @param array $connections Configuration of connections */ - protected function loadDocumentManagers(array $dmConfigs, string|null $defaultDM, string $defaultDB, ContainerBuilder $container, bool $useLazyGhostObject = false, array $connections = []): void + protected function loadDocumentManagers(array $dmConfigs, string|null $defaultDM, string $defaultDB, ContainerBuilder $container, ?string $lazyObjectSetter = null, array $connections = []): void { $dms = []; foreach ($dmConfigs as $name => $documentManager) { @@ -609,7 +612,7 @@ protected function loadDocumentManagers(array $dmConfigs, string|null $defaultDM $defaultDM, $defaultDB, $container, - $useLazyGhostObject, + $lazyObjectSetter, $connections, ); $dms[$name] = sprintf('doctrine_mongodb.odm.%s_document_manager', $name); @@ -627,7 +630,7 @@ protected function loadDocumentManagers(array $dmConfigs, string|null $defaultDM * @param ContainerBuilder $container A ContainerBuilder instance * @param array $connections Configuration of connections */ - protected function loadDocumentManager(array $documentManager, string|null $defaultDM, string $defaultDB, ContainerBuilder $container, bool $useLazyGhostObject = false, array $connections = []): void + protected function loadDocumentManager(array $documentManager, string|null $defaultDM, string $defaultDB, ContainerBuilder $container, ?string $lazyObjectSetter = null, array $connections = []): void { $connectionName = $documentManager['connection'] ?? $documentManager['name']; $configurationId = sprintf('doctrine_mongodb.odm.%s_configuration', $documentManager['name']); @@ -675,8 +678,8 @@ protected function loadDocumentManager(array $documentManager, string|null $defa $methods['setDefaultMasterKey'] = $autoEncryption['masterKey'] ?? null; } - if ($useLazyGhostObject) { - $methods['setUseLazyGhostObject'] = $useLazyGhostObject; + if ($lazyObjectSetter) { + $methods[$lazyObjectSetter] = true; } if (method_exists(ODMConfiguration::class, 'setUseTransactionalFlush')) { diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php index a0d8df49..cab2d55b 100644 --- a/tests/DependencyInjection/ConfigurationTest.php +++ b/tests/DependencyInjection/ConfigurationTest.php @@ -28,6 +28,8 @@ use function file_get_contents; use function method_exists; +use const PHP_VERSION_ID; + class ConfigurationTest extends TestCase { use ExpectDeprecationTrait; @@ -43,6 +45,7 @@ public function testDefaults(): void 'auto_generate_proxy_classes' => ODMConfiguration::AUTOGENERATE_EVAL, 'auto_generate_persistent_collection_classes' => ODMConfiguration::AUTOGENERATE_NEVER, 'enable_lazy_ghost_objects' => method_exists(ODMConfiguration::class, 'setUseLazyGhostObject'), + 'enable_native_lazy_objects' => PHP_VERSION_ID >= 80400 && method_exists(ODMConfiguration::class, 'setUseNativeLazyObject'), 'default_database' => 'default', 'document_managers' => [], 'connections' => [], @@ -78,7 +81,8 @@ public function testFullConfiguration(array $config): void 'auto_generate_hydrator_classes' => 1, 'auto_generate_proxy_classes' => ODMConfiguration::AUTOGENERATE_FILE_NOT_EXISTS, 'auto_generate_persistent_collection_classes' => ODMConfiguration::AUTOGENERATE_EVAL, - 'enable_lazy_ghost_objects' => method_exists(ODMConfiguration::class, 'setUseLazyGhostObject'), + 'enable_native_lazy_objects' => false, + 'enable_lazy_ghost_objects' => false, 'default_connection' => 'conn1', 'default_database' => 'default_db_name', 'default_document_manager' => 'default_dm_name', diff --git a/tests/DependencyInjection/Fixtures/config/xml/full.xml b/tests/DependencyInjection/Fixtures/config/xml/full.xml index f76ee3ed..555660ca 100644 --- a/tests/DependencyInjection/Fixtures/config/xml/full.xml +++ b/tests/DependencyInjection/Fixtures/config/xml/full.xml @@ -10,6 +10,8 @@ auto-generate-hydrator-classes="1" auto-generate-proxy-classes="2" auto-generate-persistent-collection-classes="3" + enable-native-lazy-objects="false" + enable-lazy-ghost-objects="false" default-connection="conn1" default-database="default_db_name" default-document-manager="default_dm_name" diff --git a/tests/DependencyInjection/Fixtures/config/yml/full.yml b/tests/DependencyInjection/Fixtures/config/yml/full.yml index d8bc2a29..acd1d202 100644 --- a/tests/DependencyInjection/Fixtures/config/yml/full.yml +++ b/tests/DependencyInjection/Fixtures/config/yml/full.yml @@ -2,6 +2,8 @@ doctrine_mongodb: auto_generate_proxy_classes: 2 auto_generate_hydrator_classes: true auto_generate_persistent_collection_classes: 3 + enable_native_lazy_objects: false + enable_lazy_ghost_objects: false default_connection: conn1 default_database: default_db_name default_document_manager: default_dm_name From e70504212525b232ac2fa733ab2a5136f095ff81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 29 Oct 2025 16:17:11 +0100 Subject: [PATCH 2/5] Fix typo --- docs/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 84374da5..31ecb3df 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -635,7 +635,7 @@ Lazy object implementation Doctrine MongoDB ODM uses lazy objects for lazy instantiation of references. The original implementation is based on the `ProxyManager` library. Since version 2.10 of Doctrine MongoDB ODM, support for Symfony lazy ghost -objects has been added. And in version 2.14, support for PHP 7.4 native lazy +objects has been added. And in version 2.14, support for PHP 8.4 native lazy objects has been added. The bundle select the best available lazy object implementation based on the From bdb0d0a8c32234cc913d478516a52a9c682f0781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 29 Oct 2025 17:14:01 +0100 Subject: [PATCH 3/5] Use Native Lary objects in tests --- tests/TestCase.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index 2b1492ef..228ebe4b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -9,11 +9,15 @@ use Doctrine\ODM\MongoDB\Mapping\Driver\AttributeDriver; use MongoDB\Client; use PHPUnit\Framework\TestCase as BaseTestCase; +use RuntimeException; use Symfony\Component\Cache\Adapter\ArrayAdapter; use function getenv; +use function method_exists; use function sys_get_temp_dir; +use const PHP_VERSION_ID; + class TestCase extends BaseTestCase { /** @param string[] $paths */ @@ -27,7 +31,14 @@ public static function createTestDocumentManager(array $paths = []): DocumentMan $config->setHydratorNamespace('SymfonyTests\Doctrine'); $config->setMetadataDriverImpl(new AttributeDriver($paths)); $config->setMetadataCache(new ArrayAdapter()); - $uri = getenv('MONGODB_URI'); + + if (PHP_VERSION_ID >= 80400 && method_exists($config, 'setUseLazyGhostObject')) { + $config->setUseLazyGhostObject(true); + } elseif (method_exists($config, 'setUseLazyGhostObject')) { + $config->setUseLazyGhostObject(false); + } + + $uri = getenv('MONGODB_URI') ?? throw new RuntimeException('The MONGODB_URI environment variable is not set.'); return DocumentManager::create(new Client($uri), $config); } From 1e56bbb9c5f7a2f1d62e7a43f40c0a46a8c8e3ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 29 Oct 2025 17:16:31 +0100 Subject: [PATCH 4/5] Fix phpstan error --- docs/config.rst | 15 +++++++-------- phpstan-baseline.neon | 20 +++++++++++++++++++- tests/TestCase.php | 2 +- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 31ecb3df..aa205307 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -634,16 +634,15 @@ Lazy object implementation Doctrine MongoDB ODM uses lazy objects for lazy instantiation of references. The original implementation is based on the `ProxyManager` library. -Since version 2.10 of Doctrine MongoDB ODM, support for Symfony lazy ghost -objects has been added. And in version 2.14, support for PHP 8.4 native lazy -objects has been added. +In version 2.10, support for Symfony lazy ghost objects has been added. +And in version 2.14, support for PHP 8.4 native lazy objects has been added. -The bundle select the best available lazy object implementation based on the +The bundle selects the best available lazy object implementation based on the installed packages and PHP version. You can override this behavior by setting -the following configuration options to enable or disable specific lazy object -implementations. This is not recommended unless you have a specific reason to do -so. Please open an issue if the default Native Lazy Objects are not working as -expected. +the following configuration options disable specific lazy object implementations. +This is not recommended unless you have a specific reason to do so. +Please open an issue if the default Native Lazy Objects are not working as expected +as it is the preferred implementation, other will be removed in future versions. - ``enable_native_lazy_objects`` is ``true`` by default when PHP 8.4+ and ``doctrine/mongodb-odm`` 2.14+ are installed. When enabled, native lazy objects will be used for lazy loading references. diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 452acb51..ad54bdf1 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -186,6 +186,12 @@ parameters: count: 2 path: src/DependencyInjection/Configuration.php + - + message: '#^Call to function method_exists\(\) with ''Doctrine\\\\ODM\\\\MongoDB\\\\Configuration'' and ''setUseNativeLazyObj…'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 2 + path: src/DependencyInjection/Configuration.php + - message: '#^Parameter \#1 \$rootNode of method Doctrine\\Bundle\\MongoDBBundle\\DependencyInjection\\Configuration\:\:addConnectionsSection\(\) expects Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition, Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition given\.$#' identifier: argument.type @@ -525,7 +531,13 @@ parameters: - message: '#^Call to function method_exists\(\) with ''Doctrine\\\\ODM\\\\MongoDB\\\\Configuration'' and ''setUseLazyGhostObje…'' will always evaluate to true\.$#' identifier: function.alreadyNarrowedType - count: 2 + count: 1 + path: tests/DependencyInjection/ConfigurationTest.php + + - + message: '#^Call to function method_exists\(\) with ''Doctrine\\\\ODM\\\\MongoDB\\\\Configuration'' and ''setUseNativeLazyObj…'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 path: tests/DependencyInjection/ConfigurationTest.php - @@ -803,3 +815,9 @@ parameters: identifier: argument.type count: 1 path: tests/ServiceRepositoryTest.php + + - + message: '#^Call to function method_exists\(\) with Doctrine\\ODM\\MongoDB\\Configuration and ''setUseLazyGhostObje…'' will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: tests/TestCase.php diff --git a/tests/TestCase.php b/tests/TestCase.php index 228ebe4b..65b504fe 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -38,7 +38,7 @@ public static function createTestDocumentManager(array $paths = []): DocumentMan $config->setUseLazyGhostObject(false); } - $uri = getenv('MONGODB_URI') ?? throw new RuntimeException('The MONGODB_URI environment variable is not set.'); + $uri = getenv('MONGODB_URI') ?: throw new RuntimeException('The MONGODB_URI environment variable is not set.'); return DocumentManager::create(new Client($uri), $config); } From c32790d65284b131a84ed0a52ea870362a334d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 29 Oct 2025 20:50:36 +0100 Subject: [PATCH 5/5] Update docs/config.rst Co-authored-by: Andreas Braun --- docs/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index aa205307..9353dc24 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -642,7 +642,7 @@ installed packages and PHP version. You can override this behavior by setting the following configuration options disable specific lazy object implementations. This is not recommended unless you have a specific reason to do so. Please open an issue if the default Native Lazy Objects are not working as expected -as it is the preferred implementation, other will be removed in future versions. +as it is the preferred implementation. The other implementations will be removed in a future version. - ``enable_native_lazy_objects`` is ``true`` by default when PHP 8.4+ and ``doctrine/mongodb-odm`` 2.14+ are installed. When enabled, native lazy objects will be used for lazy loading references.