how to deal with doctrine's lazy loading when

2019-08-04 09:42发布

i have a question about the correct way to handle doctrine's lazy loading mechanism. I have an entity that references another one via @ManyToOne:

class Entity {
    ...
    /**
     * @ManyToOne(targetEntity="AnotherEntity")
     * @JoinColumn(name="anotherEntityId", referencedColumnName="id")
     */
    protected $anotherEntity;
    ...
}

class AnotherEntity {
    /**
     * @var integer $id
     * @Column(name="id", type="integer", nullable=false)
     * @Id
     * @GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string $someValue
     * @Column(name="someValue", type="string")
     */
    private $someValue;
    ...
}

Doctrine now generates a proxy for AnotherEntity that implements lazy loading on its getters:

public function getSomeValue()
{
    $this->__load();
    return parent::getSomeValue();
}

If I now have some instances of Entity that do not have a reference counterpart of AnotherEntity ($anotherEntityId is 0 or null). Then doctrine seems to generate ONE null-Object of AnotherEntity and makes the $anotherEntity variables of all Entity object point to it. The null-object is uninitialized since we want lazy loading. I now get an EntityNotFoundException when I do this:

$entiy->getAnotherEntity()->getSomeValue();

That's great! The AnotherEntity I access does not exist in the database and so I get this exception. BUT when I do the same with the second Entity (and all others afterwards) I do not get this exception, since they are all pointing to the same instance of AnotherEntity and doctrine marked it as initialized in the first call. So now I'm accessing an object that does not exist logically but do not get any exception - I just get the uninitialized variable from getSomeValue(), e.g. an empty string.

Do you have any idea how I can make my application to recognize that the object does not exist instead of just accepting the empty value?

2条回答
太酷不给撩
2楼-- · 2019-08-04 10:18

It's a bug on doctrine 2 see here https://github.com/doctrine/doctrine2/issues/3945

The relation between user and address is one to zero or one. When a record exists on both sides, everything goes fine. When the right side (address) does not have a relevant record, calling $user->getAddress(); always returns an instance. Calling a method on that instance results in an exception:

Fatal error: Uncaught exception 'Doctrine\ORM\EntityNotFoundException' with message 'Entity of type 'Address' was not found.' in doctrine/orm/lib/Doctrine/ORM/Proxy/ProxyFactory.php on line 176

Expected behaviour: $user->getAddress() should return NULL when the right side is empty.

NOTES: Mind that the address is identified through a foreign entity (User)

查看更多
我命由我不由天
3楼-- · 2019-08-04 10:19

I guess that's a bug of doctrine, since the proxy is marked as initialized even if it is not when the EntityNotFoundException is thrown. Here is the code doctrine generates for each proxy:

public function __load()
{
    if (!$this->__isInitialized__ && $this->_entityPersister) {
        $this->__isInitialized__ = true;

        if (method_exists($this, "__wakeup")) {
            // call this after __isInitialized__to avoid infinite recursion
            // but before loading to emulate what ClassMetadata::newInstance()
            // provides.
            $this->__wakeup();
        }

        if ($this->_entityPersister->load($this->_identifier, $this) === null) {
            throw new \Doctrine\ORM\EntityNotFoundException();
        }
        unset($this->_entityPersister, $this->_identifier);
    }
}

In the file ProxyFactory you can find this code and by adding the following line just before the exception is thrown the problem is solved:

$this->__isInitialized__ = false;

With this you get the exception everytime it's needed.

Btw: the doctrine people already discussed that issue themselves but the fix does not seem to be in the current version: https://github.com/doctrine/doctrine2/pull/364

查看更多
登录 后发表回答