Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/schema/mongodb-1.0.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
<xsd:attribute name="auto-generate-hydrator-classes" type="xsd:integer" />
<xsd:attribute name="auto-generate-proxy-classes" type="xsd:integer" />
<xsd:attribute name="auto-generate-persistent-collection-classes" type="xsd:integer" />
<xsd:attribute name="enable-native-lazy-object" type="xsd:boolean" />
<xsd:attribute name="enable-lazy-ghost-object" type="xsd:boolean" />
<xsd:attribute name="default-connection" type="xsd:string" />
<xsd:attribute name="default-database" type="xsd:string" />
<xsd:attribute name="default-document-manager" type="xsd:string" />
Expand Down
44 changes: 44 additions & 0 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------------------

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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('');
Expand Down
20 changes: 19 additions & 1 deletion phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

-
Expand Down Expand Up @@ -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
29 changes: 27 additions & 2 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,6 +21,7 @@
use function preg_match;

use const JSON_THROW_ON_ERROR;
use const PHP_VERSION_ID;

/**
* FrameworkExtension configuration structure.
Expand All @@ -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')
Expand Down
19 changes: 11 additions & 8 deletions src/DependencyInjection/DoctrineMongoDBExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a bug in the previous implementation, or why does this need changing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like a bug in the previous implementation. Not covered by a test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wrong. The LazyLoadingInterface is for ProxyManager.


// load the connections
$this->loadConnections($config['connections'], $container, $config);
Expand All @@ -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'],
);

Expand Down Expand Up @@ -599,7 +602,7 @@ protected function overrideParameters(array $options, ContainerBuilder $containe
* @param ContainerBuilder $container A ContainerBuilder instance
* @param array<string, mixed> $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) {
Expand All @@ -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);
Expand All @@ -627,7 +630,7 @@ protected function loadDocumentManagers(array $dmConfigs, string|null $defaultDM
* @param ContainerBuilder $container A ContainerBuilder instance
* @param array<string, mixed> $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']);
Expand Down Expand Up @@ -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')) {
Expand Down
6 changes: 5 additions & 1 deletion tests/DependencyInjection/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
use function file_get_contents;
use function method_exists;

use const PHP_VERSION_ID;

class ConfigurationTest extends TestCase
{
use ExpectDeprecationTrait;
Expand All @@ -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' => [],
Expand Down Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions tests/DependencyInjection/Fixtures/config/xml/full.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions tests/DependencyInjection/Fixtures/config/yml/full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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);
}
Expand Down