Orm: Typed property must not be accessed before initialization

Created on 5 Dec 2019  路  19Comments  路  Source: doctrine/orm

Bug Report

| Q | A
|------------ | ------
| BC Break |no
| Version | 2.7.0

Summary

Typed property Foo::$id must not be accessed before initialization

doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:634

Seems to be an issue with reflection attempting to access typed properties.

Most helpful comment

This is relatively normal: the UnitOfWork will almost always try to access properties of entities, so if you declared a property such as private ?int $id;, if you don't want it to be set at __construct, it must have a default value through private ?int $id = null;

All 19 comments

This is relatively normal: the UnitOfWork will almost always try to access properties of entities, so if you declared a property such as private ?int $id;, if you don't want it to be set at __construct, it must have a default value through private ?int $id = null;

That totally makes sense. However... what's the recommended route forward here? Obviously you don't want the $id (PK) to be nullable. And, when calling new/instantiating the object, you generally won't know the correct value. I guess you could set it to zero, but that's a hack. Also, is this only going to be an issue with the PK/IDENTITY for Entities with typed properties?

@oojacoboo but technically $id is nullable when using any kind of auto increment identifier - its value is literally null before UnitOfWork gets the value from DB and assigns it back to the entity. I'd say it's perfect time to preach using uuid as an identifier :)

Yea, had a good discussion on Slack about this. I suspect that many other people will run into this issue as PHP 7.4 begins to be adopted more and people start typing their properties.

We'll just use protected ?int $id = null. This has a fringe benefit in allowing you to identify if the Entity has been persisted and makes sense for a default value with auto-increment on most RDBMSes.

I have the same issue, ATM i'm adopting the private ?int $id = null; strategy.

Would be nice to have a different solution than setting nullables everywhere.

@kyrrr it's totally fixable. There is an open pull request related to this, but it seems more needs to be done on it and there hasn't been much activity recently.

https://github.com/doctrine/orm/pull/7857

Updating doctrine/persistence to 1.3.6 will fix this issue.

Unfortunately, even with the updates to doctrine/persistence, ids must be nullable to be able to remove the entity, as the UnitOfWork will be setting the id value back to null in \Doctrine\ORM\UnitOfWork::executeDeletions:

// Entity with this $oid after deletion treated as NEW, even if the $oid
// is obtained by a new entity because the old one went out of scope.
//$this->entityStates[$oid] = self::STATE_NEW;
if ( ! $class->isIdentifierNatural()) {
    $class->reflFields[$class->identifier[0]]->setValue($entity, null);
}

I've filed this issue: #7999

using this: "name": "doctrine/persistence", "version": "1.3.6",
but the problem (as described before) is still there when trying to flush

Can the property just be unset or, by using reflection, check if nullable and optionally set as null?

Deletion is an interesting scenario. Technically, unless using soft-delete, that object shouldn't really exist anymore, and certainly not the ID. I can see how it'd be useful to use an object after deletion though.

My error:

"Typed property Proxies\\__CG__\\App\\Entity\\Organization::$ must not be accessed before initialization (in __sleep)"

My solution - add next method to the class:

public function __sleep()
{
    return [];
}

Updating doctrine/persistence to 1.3.6 will fix this issue.

it doesn't

What about properties where you can't set the default value, like in associations that use collection? Object cannot be a default value, and nullable makes no sense here.

This example:

class Entity {
        /**
     * @ORM\OneToMany(targetEntity="Other")
     * @var Collection|Other[]
     */
    protected $friends;
    public function __construct() {
        $this->friends = new ArrayCollection();
    }
}

used to be the proper way to initialize collections, but now if you add PHP7.4 property type hints (protected Collection $friends;) it no longer works, as Doctrine accesses the property through reflection but doesn't run the constructor and then you get the Fatal Error.

And that's not the only example, what about custom Doctrine types that use some object to hold the value?

but now if you add PHP7.4 property type hints (protected Collection $friends;) it no longer works, as Doctrine accesses the property through reflection but doesn't run the constructor and then you get the Fatal Error.

Proxy initialization does set collections?

What doesn't work (and also didn't work in the past) in the 2.x series is "friend objects", but otherwise proxy initialization does populate their associations.

This is the broken behavior (also pre-php-7.4):

class Thing
{
    private $id;
    private $lazyField;
    function equals(self $other) {
        return $other->lazyField->equals($this->lazyField); // call to `equals` on `null` happening here
    }
}

@Ocramius That's partially right. But, you don't need the call to equals there. You only need the call on lazyField, which is the issue. It's of particular concern for auto incremented IDs. You make it sound like this has always been an issue and it's not an important one.

Proxy initialization does set collections?

Interesting, for some reason I have this issue only in our Jenkins test build but can't replicate it locally. I'll investigate further.

@Ocramius That's partially right. But, you don't need the call to equals there. You only need the call on lazyField, which is the issue. It's of particular concern for auto incremented IDs. You make it sound like this has always been an issue and it's not an important one.

@oojacoboo yes, I made the example to show that the method call will lead to a crash: much harder to notice if I used === there :)

Alright, investigation concluded; when clearing cache on Jenkins no APP_ENV was defined so it cleared the default, but we needed other envs cleared. This lead to old metadata being used from cache and in those Doctrine didn't even know about some of the fields, skipping the initialization.

Sorry, my mistake.

Was this page helpful?
0 / 5 - 0 ratings