orm: Problem with Proxies on PHP 7.4.2 with typed properties

(I’m sorry if this is wrong place to report that issue, please transfer me to the proper one if this is the case)

We encountered this problem on the Behat testing phase with generated entity proxies. There is fatal error:

Typed property Proxies_CG_\App\Entity\EntityName:😒 must not be accessed before initialization (in __sleep)

This happened when we upgraded PHP to 7.4.2 (everything was ok on 7.4.1). We used typed properties before the upgrade as well. After playing with the entity for a bit we discovered that now every typed object property must be initialized immediately (previously they were initialized in the constructor) like:

private ?DateTimeInterface $property = null;

public function __construct()
{
    $this->property = new DateTime();
}

because this doesn’t work anymore:

private DateTimeInterface $property;

public function __construct()
{
    $this->property = new DateTime();
}

This is was already reported in Symfony https://github.com/symfony/symfony/issues/35574 but closed.

Installed Doctrine package Version
doctrine/annotations v1.8.0
doctrine/cache 1.10.0
doctrine/collections 1.6.4
doctrine/common 2.12.0
doctrine/data-fixtures 1.4.2
doctrine/dbal v2.10.1
doctrine/doctrine-bundle 2.0.7
doctrine/doctrine-migrations-bundle 2.1.2
doctrine/event-manager 1.1.0
doctrine/inflector 1.3.1
doctrine/instantiator 1.3.0
doctrine/lexer 1.2.0
doctrine/migrations 2.2.1
doctrine/mongodb-odm 2.0.5
doctrine/mongodb-odm-bundle 4.1.0
doctrine/orm v2.7.1
doctrine/persistence 1.3.6
doctrine/reflection v1.1.0

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 25 (10 by maintainers)

Commits related to this issue

Most upvoted comments

I was wondering why doctrine/common’s ProxyLogicTypedPropertiesTest passes when there is at least one uninitialized typed property in tested object and its proxy is serialized in the test.

Turns out that every uninitialized field is actually being initialized in the setup phase of that test which is why this issue I’ve reported goes undetected. You can easily verify it by adding additional public string $test; in LazyLoadableObjectWithTypedProperties and running Doctrine\Tests\Common\Proxy\ProxyLogicTypedPropertiesTest::testNotInitializedProxyUnserialization. The result is then

Error: Typed property Doctrine\Tests\Common\ProxyProxy_CG_\Doctrine\Tests\Common\Proxy\LazyLoadableObjectWithTypedProperties::$test must not be accessed before initialization (in __sleep)

And please don’t get me wrong, I know this test is to verify lazy loading and properties should be initialized but still I think it makes my point.

Will it be enough for reproducible test case @beberlei ? Once again sorry for sounding harsh before. You guys are doing great job, and I really appreciate it.

Sorry for the late reply @beberlei. Thanks to @alanpoulain it’s gonna be fixed in Symfony: https://github.com/symfony/symfony/pull/36332

My PR hasn’t been merged in Symfony. Has you can see in the comments, not everybody agree that it’s the proper fix.

@bizley

This example given in your first comment:

private ?DateTimeInterface $property = null;

will delay the issue presenting itself, but as described in the comment by @EmanuelOster using PUT and PATCH will make it show up again.

Consequently, even though it was tongue-in-cheek, this won’t actually work:

To tell everyone to initialize immediately every entity’s typed property.

Do not use private properties in entities: Projects/ORM/Documentation/Architecture#Serializing entities. Doctrine does not have full support for that.

If you intend to serialize (and unserialize) entity instances that still hold references to proxy objects you may run into problems with private properties because of technical limitations. Proxy objects implement __sleep and it is not possible for __sleep to return names of private properties in parent classes.

Ok, now I understand why it’s happening in PHP 7.4.2 - https://bugs.php.net/bug.php?id=79002

Turns out serialize with __sleep was behaving incorrectly before and allowing objects with uninitialized properties to be serialized (unserialize() was throwing TypeError then) and it was fixed in 7.4.2 (https://www.php.net/ChangeLog-7.php).

Right now I see 3 possible solutions:

  1. To catch Error in AbstractObjectNormalizer::getCacheKey (like @EmanuelOster suggested) and return false there since object with uninitialized property can not be serialized anyway).
  2. To tell everyone to initialize immediately every entity’s typed property.
  3. To change doctrine proxy to work around that (I don’t know how, maybe add checks like ReflectionProperty::isInitialized).