test persist an uninitialized lazy ghost
We've the use case inside API Platform where the JsonStreamer deserializes a JSON into a Lazy Ghost as while its not consumed (streaming) we don't need to initialize this object. Therefore at some point we try to persist an uninitialized lazy ghost and it'll likely throw "column title must not be null".
public function testPersistLazyGhost(): void
{
if (PHP_VERSION_ID < 80400) {
$this->markTestSkipped('Lazy objects are only available in PHP 8.4+.');
}
$initialized = false;
$reflector = new \ReflectionClass(PersistentEntity::class);
$lazyGhost = $reflector->newLazyGhost(function (PersistentEntity $object) use (&$initialized) {
$initialized = true;
$object->setName('LazyGhostInitialized');
});
self::assertFalse($initialized, 'Lazy ghost should not be initialized before persist.');
$this->_em->persist($lazyGhost);
$this->_em->flush();
self::assertTrue($initialized, 'Lazy ghost should be initialized during flush.');
$this->_em->clear();
$retrievedEntity = $this->_em->find(PersistentEntity::class, $lazyGhost->getId());
self::assertNotNull($retrievedEntity);
self::assertEquals('LazyGhostInitialized', $retrievedEntity->name);
}
@beberlei @greg0ire
I've spoken to @soyuka about this issue. The issue is that app tries to persist lazy objects as new records. Those are lazy objects that are not Doctrine proxies. Because we read the object's values through reflection, we don't initialize the objects and basically read null everywhere.
How shall we handle this? Should we attempt to detect lazy objects and initialize them?
I'm sorry but I don't think I get it.
the JsonStreamer deserializes a JSON into a Lazy Ghost as while its not consumed (streaming) we don't need to initialize this object
So you're receiving JSON from an http request and stream that data into several entities, not all of which you persist, hence why you can't afford to initialize them?
I can initialize it but I was wondering if Doctrine should initialize it itself if its persisted, as right now if you attempt to persist a lazy ghost entity (that wasn't created by Doctrine) then it fails and never initializes it (as doctrine uses reflection).
I think there are at least 2 point of views:
a) If we pass an entity to the ORM, it should be able to deal with it regardless of whether it's lazy or not. It shouldn't matter that the object is lazy. b) If we pass a lazy object to the ORM, and it becomes non-lazy, that's a side effect, and it's unclear whether that side effect is OK, so maybe an exception should be thrown forbidding to force users to make their objects non-lazy explicitly, outside Doctrine.
Regardless of the answer, it seems that we should attempt to detect whether objects are lazy or not. Do we have a way to do that that is reliable and where performance won't be a concern?
Regardless of the answer, it seems that we should attempt to detect whether objects are lazy or not. Do we have a way to do that that is reliable and where performance won't be a concern?
Yes, we can use reflection to detect a lazy object. And since we're already accessing the object's properties through reflection, one more check won't hurt, I guess.
I think I lean more towards scenario b. What about you?
If I pass an object to the ORM via the persist() method, I believe I do that with the intention of having it read by the ORM. So maybe initializing the object is okay or even expected?
And usually, the laziness would be broken by reading the properties. It's more or less an accident that it does not happen in our case.
True. So how do we trigger the initialization then?
Something like this?
if (\PHP_VERSION_ID > 80400) {
$r = new \ReflectionClass($value);
if ($r->isUninitializedLazyObject($value)) {
$r->initializeLazyObject($value);
}
}
https://github.com/api-platform/core/blob/45831a93c9d256d9ebecd3db13ac7db34e3778f1/src/Doctrine/Common/State/PersistProcessor.php#L116-L121
Looks good. Please push a commit with that.