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..9353dc24 100644
--- a/docs/config.rst
+++ b/docs/config.rst
@@ -629,6 +629,46 @@ 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.
+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 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 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. 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.
+- ``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 +779,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 +856,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/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/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
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 2b1492ef..65b504fe 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);
}