Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8189714
Import all PropertyAccessor classes from ORM
GromNaN Sep 26, 2025
c42217e
Replace ReflectionField with PropertyAccessor
GromNaN Sep 26, 2025
40f56f7
Compatibility PHP < 8.4
GromNaN Sep 26, 2025
c73c94b
Import tests of PropertyAccessors
GromNaN Sep 27, 2025
b7631ed
Add support for PHP 8.4 Lazy Objects with configuration flag
GromNaN Sep 27, 2025
a8f2ea9
Move classes requiring PHP 8.4 outside of the Documents directory, as…
GromNaN Sep 27, 2025
8554f69
Use a WeakMap to track generated lazy objects
GromNaN Sep 28, 2025
a588e0b
Clear ClassMetadata::(get|set)FieldValue using propertyAccessors
GromNaN Sep 28, 2025
4a263e3
Fix initialization of lazy objects
GromNaN Oct 8, 2025
718170f
skip identifier field in computeChangeSet
GromNaN Oct 8, 2025
2e7d87e
Fix CS
GromNaN Oct 8, 2025
c32bafc
initialize lazy object when getting/setting a prop value
GromNaN Oct 8, 2025
cc70391
Mark lazy object as initialized when hydrated
GromNaN Oct 8, 2025
d38bbdf
Fix updating id on embedded documents
GromNaN Oct 8, 2025
6987219
Enable event listeners in initializer
GromNaN Oct 8, 2025
368dd4c
Named arguments not allowed in PHPUnit attributes
GromNaN Oct 8, 2025
fe87c12
CS fixes
GromNaN Oct 8, 2025
15dabfb
Remove unused class
GromNaN Oct 9, 2025
5a20551
Fix support for GhostObjectInterface
GromNaN Oct 9, 2025
b2dd58b
Improve LegacyReflectionFields
GromNaN Oct 9, 2025
cc40513
Mapping virtual property is not supported
GromNaN Oct 9, 2025
88f7dee
Remove unused $reflectionService property
GromNaN Oct 11, 2025
69436d9
Revert wakeupReflection
GromNaN Oct 13, 2025
c938489
Baseline phpstan false-positive
GromNaN Oct 13, 2025
2aa3e69
Update deprecation version numbers
GromNaN Oct 20, 2025
b91d647
More tests
GromNaN Oct 20, 2025
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
8 changes: 8 additions & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ jobs:
dependencies: "highest"
symfony-version: "stable"
proxy: "proxy-manager"
# Test with Native Lazy Objects
- php-version: "8.4"
mongodb-version: "8.0"
driver-version: "stable"
dependencies: "highest"
symfony-version: "stable"
proxy: "native"
# Test with extension 1.21
- topology: "server"
php-version: "8.2"
Expand Down Expand Up @@ -163,4 +170,5 @@ jobs:
env:
DOCTRINE_MONGODB_SERVER: ${{ steps.setup-mongodb.outputs.cluster-uri }}
USE_LAZY_GHOST_OBJECTS: ${{ matrix.proxy == 'lazy-ghost' && '1' || '0' }}"
USE_NATIVE_LAZY_OBJECTS: ${{ matrix.proxy == 'native' && '1' || '0' }}"
CRYPT_SHARED_LIB_PATH: ${{ steps.setup-mongodb.outputs.crypt-shared-lib-path }}
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"Doctrine\\ODM\\MongoDB\\Tests\\": "tests/Doctrine/ODM/MongoDB/Tests",
"Documentation\\": "tests/Documentation",
"Documents\\": "tests/Documents",
"Documents84\\": "tests/Documents84",
"Stubs\\": "tests/Stubs",
"TestDocuments\\" :"tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures"
}
Expand Down
44 changes: 35 additions & 9 deletions lib/Doctrine/ODM/MongoDB/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
use function trigger_deprecation;
use function trim;

use const PHP_VERSION_ID;

/**
* Configuration class for the DocumentManager. When setting up your DocumentManager
* you can optionally specify an instance of this class as the second argument.
Expand Down Expand Up @@ -145,7 +147,8 @@ class Configuration

private bool $useTransactionalFlush = false;

private bool $useLazyGhostObject = false;
private bool $lazyGhostObject = false;
private bool $nativeLazyObjects = false;

private static string $version;

Expand Down Expand Up @@ -686,26 +689,49 @@ public function isTransactionalFlushEnabled(): bool
* Generate proxy classes using Symfony VarExporter's LazyGhostTrait if true.
* Otherwise, use ProxyManager's LazyLoadingGhostFactory (deprecated)
*/
public function setUseLazyGhostObject(bool $flag): void
public function setLazyGhostObject(bool $flag): void
{
if ($this->nativeLazyObjects) {
throw new LogicException('Cannot enable or disable LazyGhostObject when native lazy objects are enabled.');
}

if ($flag === false) {
if (! class_exists(ProxyManagerConfiguration::class)) {
throw new LogicException('Package "friendsofphp/proxy-manager-lts" is required to disable LazyGhostObject.');
}

trigger_deprecation(
'doctrine/mongodb-odm',
'2.10',
'Using "friendsofphp/proxy-manager-lts" is deprecated. Use "symfony/var-exporter" LazyGhostObjects instead.',
);
trigger_deprecation('doctrine/mongodb-odm', '2.10', 'Using "friendsofphp/proxy-manager-lts" is deprecated. Use "symfony/var-exporter" LazyGhostObjects instead.');
}

$this->useLazyGhostObject = $flag;
if ($flag === true && PHP_VERSION_ID >= 80400) {
trigger_deprecation('doctrine/mongodb-odm', '2.14', 'Using "symfony/var-exporter" lazy ghost objects is deprecated and will be impossible in Doctrine MongoDB ODM 3.0.');
}

$this->lazyGhostObject = $flag;
}

public function isLazyGhostObjectEnabled(): bool
{
return $this->useLazyGhostObject;
return $this->lazyGhostObject;
}

public function enableNativeLazyObjects(bool $nativeLazyObjects): void
{
if (PHP_VERSION_ID >= 80400 && ! $nativeLazyObjects) {
trigger_deprecation('doctrine/mongodb-odm', '2.14', 'Disabling native lazy objects is deprecated and will be impossible in Doctrine MongoDB ODM 3.0.');
}

if (PHP_VERSION_ID < 80400 && $nativeLazyObjects) {
throw new LogicException('Lazy loading proxies require PHP 8.4 or higher.');
}

$this->nativeLazyObjects = $nativeLazyObjects;
$this->lazyGhostObject = ! $nativeLazyObjects || $this->lazyGhostObject;
}

public function isNativeLazyObjectsEnabled(): bool
{
return $this->nativeLazyObjects;
}

/**
Expand Down
19 changes: 10 additions & 9 deletions lib/Doctrine/ODM/MongoDB/DocumentManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactoryInterface;
use Doctrine\ODM\MongoDB\Mapping\MappingException;
use Doctrine\ODM\MongoDB\Proxy\Factory\LazyGhostProxyFactory;
use Doctrine\ODM\MongoDB\Proxy\Factory\NativeLazyObjectFactory;
use Doctrine\ODM\MongoDB\Proxy\Factory\ProxyFactory;
use Doctrine\ODM\MongoDB\Proxy\Factory\StaticProxyFactory;
use Doctrine\ODM\MongoDB\Proxy\Resolver\CachingClassNameResolver;
Expand All @@ -31,7 +32,6 @@
use MongoDB\Driver\ClientEncryption;
use MongoDB\Driver\ReadPreference;
use MongoDB\GridFS\Bucket;
use ProxyManager\Proxy\GhostObjectInterface;
use RuntimeException;
use Throwable;

Expand Down Expand Up @@ -179,11 +179,13 @@ protected function __construct(?Client $client = null, ?Configuration $config =
$this->config->getAutoGenerateHydratorClasses(),
);

$this->unitOfWork = new UnitOfWork($this, $this->eventManager, $this->hydratorFactory);
$this->schemaManager = new SchemaManager($this, $this->metadataFactory);
$this->proxyFactory = $this->config->isLazyGhostObjectEnabled()
? new LazyGhostProxyFactory($this, $this->config->getProxyDir(), $this->config->getProxyNamespace(), $this->config->getAutoGenerateProxyClasses())
: new StaticProxyFactory($this);
$this->unitOfWork = new UnitOfWork($this, $this->eventManager, $this->hydratorFactory);
$this->schemaManager = new SchemaManager($this, $this->metadataFactory);
$this->proxyFactory = match (true) {
$this->config->isNativeLazyObjectsEnabled() => new NativeLazyObjectFactory($this),
$this->config->isLazyGhostObjectEnabled() => new LazyGhostProxyFactory($this, $this->config->getProxyDir(), $this->config->getProxyNamespace(), $this->config->getAutoGenerateProxyClasses()),
default => new StaticProxyFactory($this),
};
$this->repositoryFactory = $this->config->getRepositoryFactory();
}

Expand Down Expand Up @@ -607,7 +609,7 @@ public function flush(array $options = []): void
* @param mixed $identifier
* @param class-string<T> $documentName
*
* @return T|(T&GhostObjectInterface<T>)
* @return T
*
* @template T of object
*/
Expand All @@ -624,9 +626,8 @@ public function getReference(string $documentName, $identifier): object
return $document;
}

/** @var T&GhostObjectInterface<T> $document */
$document = $this->proxyFactory->getProxy($class, $identifier);
$this->unitOfWork->registerManaged($document, $identifier, []);
$this->unitOfWork->registerManaged($document, $identifier, [$class->identifier => $identifier]);

return $document;
}
Expand Down
19 changes: 12 additions & 7 deletions lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
use function uniqid;

use const DIRECTORY_SEPARATOR;
use const PHP_VERSION_ID;

/**
* The HydratorFactory class is responsible for instantiating a correct hydrator
Expand Down Expand Up @@ -187,7 +188,7 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
if (array_key_exists('%1$s', $data) && ($data['%1$s'] !== null || ($this->class->fieldMappings['%2$s']['nullable'] ?? false))) {
$value = $data['%1$s'];
%3$s
$this->class->reflFields['%2$s']->setValue($document, $return === null ? null : clone $return);
$this->class->propertyAccessors['%2$s']->setValue($document, $return === null ? null : clone $return);
$hydratedData['%2$s'] = $return;
}

Expand All @@ -210,7 +211,7 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
} else {
\$return = null;
}
\$this->class->reflFields['%2\$s']->setValue(\$document, \$return);
\$this->class->propertyAccessors['%2\$s']->setValue(\$document, \$return);
\$hydratedData['%2\$s'] = \$return;
}

Expand Down Expand Up @@ -239,7 +240,7 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
$return = $this->dm->getReference($className, $id);
}

$this->class->reflFields['%2$s']->setValue($document, $return);
$this->class->propertyAccessors['%2$s']->setValue($document, $return);
$hydratedData['%2$s'] = $return;
}

Expand All @@ -256,7 +257,7 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla

$className = $this->class->fieldMappings['%2$s']['targetDocument'];
$return = $this->dm->getRepository($className)->%3$s($document);
$this->class->reflFields['%2$s']->setValue($document, $return);
$this->class->propertyAccessors['%2$s']->setValue($document, $return);
$hydratedData['%2$s'] = $return;

EOF
Expand All @@ -280,7 +281,7 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
);
$sort = $this->class->fieldMappings['%2$s']['sort'] ?? [];
$return = $this->dm->getUnitOfWork()->getDocumentPersister($className)->load($criteria, null, [], 0, $sort);
$this->class->reflFields['%2$s']->setValue($document, $return);
$this->class->propertyAccessors['%2$s']->setValue($document, $return);
$hydratedData['%2$s'] = $return;

EOF
Expand All @@ -307,7 +308,7 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
if ($mongoData) {
$return->setMongoData($mongoData);
}
$this->class->reflFields['%2$s']->setValue($document, $return);
$this->class->propertyAccessors['%2$s']->setValue($document, $return);
$hydratedData['%2$s'] = $return;

EOF
Expand Down Expand Up @@ -345,7 +346,7 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
}
}

$this->class->reflFields['%2$s']->setValue($document, $return);
$this->class->propertyAccessors['%2$s']->setValue($document, $return);
$hydratedData['%2$s'] = $return;
}

Expand Down Expand Up @@ -450,6 +451,10 @@ public function hydrate(object $document, array $data, array $hints = []): array
}
}

if (PHP_VERSION_ID >= 80400) {
$metadata->reflClass->markLazyObjectAsInitialized($document);
}

if ($document instanceof InternalProxy) {
// Skip initialization to not load any object data
$document->__setInitialized(true);
Expand Down
Loading