Skip to content

Commit 63f6593

Browse files
authored
Add support for PHP 8.4 Lazy Objects with configuration flag (#2840)
1 parent ae9ec3d commit 63f6593

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1594
-121
lines changed

.github/workflows/atlas-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,4 @@ jobs:
9696
run: "vendor/bin/phpunit --group atlas"
9797
env:
9898
DOCTRINE_MONGODB_SERVER: "mongodb://127.0.0.1:27017/?directConnection=true"
99-
USE_LAZY_GHOST_OBJECTS: ${{ matrix.proxy == 'lazy-ghost' && '1' || '0' }}
99+
USE_LAZY_GHOST_OBJECT: ${{ matrix.proxy == 'lazy-ghost' && '1' || '0' }}

.github/workflows/continuous-integration.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ jobs:
7777
dependencies: "highest"
7878
symfony-version: "stable"
7979
proxy: "proxy-manager"
80+
# Test with Native Lazy Objects
81+
- php-version: "8.4"
82+
mongodb-version: "8.0"
83+
driver-version: "stable"
84+
dependencies: "highest"
85+
symfony-version: "stable"
86+
proxy: "native"
8087
# Test with extension 1.21
8188
- topology: "server"
8289
php-version: "8.2"
@@ -162,5 +169,6 @@ jobs:
162169
run: "vendor/bin/phpunit --exclude-group=atlas"
163170
env:
164171
DOCTRINE_MONGODB_SERVER: ${{ steps.setup-mongodb.outputs.cluster-uri }}
165-
USE_LAZY_GHOST_OBJECTS: ${{ matrix.proxy == 'lazy-ghost' && '1' || '0' }}"
172+
USE_LAZY_GHOST_OBJECT: ${{ matrix.proxy == 'lazy-ghost' && '1' || '0' }}"
173+
USE_NATIVE_LAZY_OBJECT: ${{ matrix.proxy == 'native' && '1' || '0' }}"
166174
CRYPT_SHARED_LIB_PATH: ${{ steps.setup-mongodb.outputs.crypt-shared-lib-path }}

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"Doctrine\\ODM\\MongoDB\\Tests\\": "tests/Doctrine/ODM/MongoDB/Tests",
7272
"Documentation\\": "tests/Documentation",
7373
"Documents\\": "tests/Documents",
74+
"Documents84\\": "tests/Documents84",
7475
"Stubs\\": "tests/Stubs",
7576
"TestDocuments\\" :"tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/fixtures"
7677
}

lib/Doctrine/ODM/MongoDB/Configuration.php

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
use function trigger_deprecation;
4848
use function trim;
4949

50+
use const PHP_VERSION_ID;
51+
5052
/**
5153
* Configuration class for the DocumentManager. When setting up your DocumentManager
5254
* you can optionally specify an instance of this class as the second argument.
@@ -145,7 +147,8 @@ class Configuration
145147

146148
private bool $useTransactionalFlush = false;
147149

148-
private bool $useLazyGhostObject = false;
150+
private bool $lazyGhostObject = false;
151+
private bool $nativeLazyObject = false;
149152

150153
private static string $version;
151154

@@ -688,24 +691,47 @@ public function isTransactionalFlushEnabled(): bool
688691
*/
689692
public function setUseLazyGhostObject(bool $flag): void
690693
{
694+
if ($this->nativeLazyObject) {
695+
throw new LogicException('Cannot enable or disable LazyGhostObject when native lazy objects are enabled.');
696+
}
697+
691698
if ($flag === false) {
692699
if (! class_exists(ProxyManagerConfiguration::class)) {
693700
throw new LogicException('Package "friendsofphp/proxy-manager-lts" is required to disable LazyGhostObject.');
694701
}
695702

696-
trigger_deprecation(
697-
'doctrine/mongodb-odm',
698-
'2.10',
699-
'Using "friendsofphp/proxy-manager-lts" is deprecated. Use "symfony/var-exporter" LazyGhostObjects instead.',
700-
);
703+
trigger_deprecation('doctrine/mongodb-odm', '2.10', 'Using "friendsofphp/proxy-manager-lts" is deprecated. Use "symfony/var-exporter" LazyGhostObjects instead.');
701704
}
702705

703-
$this->useLazyGhostObject = $flag;
706+
if ($flag === true && PHP_VERSION_ID >= 80400) {
707+
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.');
708+
}
709+
710+
$this->lazyGhostObject = $flag;
704711
}
705712

706713
public function isLazyGhostObjectEnabled(): bool
707714
{
708-
return $this->useLazyGhostObject;
715+
return $this->lazyGhostObject;
716+
}
717+
718+
public function setUseNativeLazyObject(bool $nativeLazyObject): void
719+
{
720+
if (PHP_VERSION_ID < 80400 && $nativeLazyObject) {
721+
throw new LogicException('Native lazy objects require PHP 8.4 or higher.');
722+
}
723+
724+
$this->nativeLazyObject = $nativeLazyObject;
725+
$this->lazyGhostObject = ! $nativeLazyObject || $this->lazyGhostObject;
726+
}
727+
728+
public function isNativeLazyObjectEnabled(): bool
729+
{
730+
if (PHP_VERSION_ID >= 80400 && ! $this->nativeLazyObject) {
731+
trigger_deprecation('doctrine/mongodb-odm', '2.14', 'Not using native lazy objects is deprecated and will be impossible in Doctrine MongoDB ODM 3.0.');
732+
}
733+
734+
return $this->nativeLazyObject;
709735
}
710736

711737
/**

lib/Doctrine/ODM/MongoDB/DocumentManager.php

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactoryInterface;
1111
use Doctrine\ODM\MongoDB\Mapping\MappingException;
1212
use Doctrine\ODM\MongoDB\Proxy\Factory\LazyGhostProxyFactory;
13+
use Doctrine\ODM\MongoDB\Proxy\Factory\NativeLazyObjectFactory;
1314
use Doctrine\ODM\MongoDB\Proxy\Factory\ProxyFactory;
1415
use Doctrine\ODM\MongoDB\Proxy\Factory\StaticProxyFactory;
1516
use Doctrine\ODM\MongoDB\Proxy\Resolver\CachingClassNameResolver;
@@ -31,7 +32,6 @@
3132
use MongoDB\Driver\ClientEncryption;
3233
use MongoDB\Driver\ReadPreference;
3334
use MongoDB\GridFS\Bucket;
34-
use ProxyManager\Proxy\GhostObjectInterface;
3535
use RuntimeException;
3636
use Throwable;
3737

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

182-
$this->unitOfWork = new UnitOfWork($this, $this->eventManager, $this->hydratorFactory);
183-
$this->schemaManager = new SchemaManager($this, $this->metadataFactory);
184-
$this->proxyFactory = $this->config->isLazyGhostObjectEnabled()
185-
? new LazyGhostProxyFactory($this, $this->config->getProxyDir(), $this->config->getProxyNamespace(), $this->config->getAutoGenerateProxyClasses())
186-
: new StaticProxyFactory($this);
182+
$this->unitOfWork = new UnitOfWork($this, $this->eventManager, $this->hydratorFactory);
183+
$this->schemaManager = new SchemaManager($this, $this->metadataFactory);
184+
$this->proxyFactory = match (true) {
185+
$this->config->isNativeLazyObjectEnabled() => new NativeLazyObjectFactory($this),
186+
$this->config->isLazyGhostObjectEnabled() => new LazyGhostProxyFactory($this, $this->config->getProxyDir(), $this->config->getProxyNamespace(), $this->config->getAutoGenerateProxyClasses()),
187+
default => new StaticProxyFactory($this),
188+
};
187189
$this->repositoryFactory = $this->config->getRepositoryFactory();
188190
}
189191

@@ -607,7 +609,7 @@ public function flush(array $options = []): void
607609
* @param mixed $identifier
608610
* @param class-string<T> $documentName
609611
*
610-
* @return T|(T&GhostObjectInterface<T>)
612+
* @return T
611613
*
612614
* @template T of object
613615
*/
@@ -624,9 +626,8 @@ public function getReference(string $documentName, $identifier): object
624626
return $document;
625627
}
626628

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

631632
return $document;
632633
}

lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use function uniqid;
3434

3535
use const DIRECTORY_SEPARATOR;
36+
use const PHP_VERSION_ID;
3637

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

@@ -210,7 +211,7 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
210211
} else {
211212
\$return = null;
212213
}
213-
\$this->class->reflFields['%2\$s']->setValue(\$document, \$return);
214+
\$this->class->propertyAccessors['%2\$s']->setValue(\$document, \$return);
214215
\$hydratedData['%2\$s'] = \$return;
215216
}
216217
@@ -239,7 +240,7 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
239240
$return = $this->dm->getReference($className, $id);
240241
}
241242
242-
$this->class->reflFields['%2$s']->setValue($document, $return);
243+
$this->class->propertyAccessors['%2$s']->setValue($document, $return);
243244
$hydratedData['%2$s'] = $return;
244245
}
245246

@@ -256,7 +257,7 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
256257
257258
$className = $this->class->fieldMappings['%2$s']['targetDocument'];
258259
$return = $this->dm->getRepository($className)->%3$s($document);
259-
$this->class->reflFields['%2$s']->setValue($document, $return);
260+
$this->class->propertyAccessors['%2$s']->setValue($document, $return);
260261
$hydratedData['%2$s'] = $return;
261262

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

286287
EOF
@@ -307,7 +308,7 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
307308
if ($mongoData) {
308309
$return->setMongoData($mongoData);
309310
}
310-
$this->class->reflFields['%2$s']->setValue($document, $return);
311+
$this->class->propertyAccessors['%2$s']->setValue($document, $return);
311312
$hydratedData['%2$s'] = $return;
312313

313314
EOF
@@ -345,7 +346,7 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
345346
}
346347
}
347348
348-
$this->class->reflFields['%2$s']->setValue($document, $return);
349+
$this->class->propertyAccessors['%2$s']->setValue($document, $return);
349350
$hydratedData['%2$s'] = $return;
350351
}
351352

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

454+
if (PHP_VERSION_ID >= 80400) {
455+
$metadata->reflClass->markLazyObjectAsInitialized($document);
456+
}
457+
453458
if ($document instanceof InternalProxy) {
454459
// Skip initialization to not load any object data
455460
$document->__setInitialized(true);

0 commit comments

Comments
 (0)