Doctrine不能正确地从会话中加载关联


Doctrine doesn't load associations from session correctly

我有这种行为与原则2.1,我正在寻找一个很好的"变通办法"。问题如下:

我有一个用户实体:

/**
 * @Entity(repositoryClass="Application'Entity'Repository'UserRepository")
 * @HasLifecycleCallbacks
 */
class User extends AbstractEntity
{
    /**
     * 
     * @var integer
     * 
     * @Column(type="integer",nullable=false)
     * @Id
     * @GeneratedValue(strategy="IDENTITY")
     */
    protected $id;
    /**
     * 
     * @var 'DateTime
     * @Column(type="datetime",nullable=false)   
     */
    protected $insertDate;
    /**
     *
     * @var string
     * @Column(type="string", nullable=false)
     */
    protected $username;
    /**
     *
     * @ManyToOne(targetEntity="UserGroup", cascade={"merge"})
     */
    protected $userGroup;
}

用户组实体:

/**
 * @Entity
 */
class UserGroup extends AbstractEntity
{
    /**
     * 
     * @var integer
     * 
     * @Column(type="integer",nullable=false)
     * @Id
     * @GeneratedValue(strategy="IDENTITY")
     */
    protected $id;
    /**
     * 
     * @var string
     * @Column(type="string",nullable=false)     
     */
    protected $name;   
}

如果我实例化一个用户对象(用Zend_Auth这样做),Zend_Auth将它自动放入会话。

然而,问题是,如果我在下一页从会话中拉回来,那么用户类中的数据是完全加载的,但不是在userGroup关联中。如果我在用户对象的注释中添加cascade={"merge"},则加载userGroup对象,但数据为空。如果您转储如下内容:

$user->userGroup->name

你将得到NULL返回。问题是在用户对象保存在会话中之前没有访问用户组实体的数据,因此将返回一个空的初始化对象。如果我这样做:

echo $user->userGroup->name;

在我将用户对象存储在会话中之前,关联userGroup的所有数据都已成功保存,并且如果我尝试访问$user->userGroup->name变量,则不会在下一页返回NULL。

有一个简单的方法来解决这个问题吗?我可以手动加载userGroup对象/关联与生命周期回调@onLoad在用户类也许?有什么建议吗?

您的问题是mjh_ca回答的问题和您的AbstractEntity实现的问题的组合。

既然您显示您以这种方式访问实体字段:

$user->userGroup->name;

我假设你的AbstractEntity基类使用__get()__set()魔法方法,而不是适当的getter和setter:

function getUserGroup()
{
    return $this->userGroup;
}
function setUserGroup(UserGroup $userGroup)
{
    $this->userGroup = $userGroup;
}

你实际上打破了延迟加载:

"……当您访问尚未初始化的代理对象的公共属性时,返回值将为空。Doctrine不能挂接到这个进程并神奇地使实体惰性加载。"

最佳实践:不要在实体上使用公共属性

你应该这样访问字段:

$user->getUserGroup()->getName();

问题的第二部分与mjh_ca所写的完全相同- Zend_Auth在将实体序列化以在会话中存储时将实体与实体管理器分离。在关联上设置cascade={"merge"}将不起作用,因为它是分离的实际实体。您必须将反序列化的User实体合并到实体管理器中。

$detachedIdentity = Zend_Auth::getInstance()->getIdentity();
$identity = $em->merge($detachedIdentity);

问题是如何干净利落地做到这一点。您可以考虑为您的User实体实现__wakeup()魔法方法,但这也违反了理论最佳实践…

来源:实现唤醒或克隆

既然我们谈论的是Zend_Auth,你可以扩展Zend_Auth并覆盖getIdentity()函数,以便它是实体感知的。

use Doctrine'ORM'EntityManager,
    Doctrine'ORM'UnitOfWork;
class My_Auth extends 'Zend_Auth
{
    protected $_entityManager;
    /**
     * override otherwise self::$_instance
     * will still create an instance of Zend_Auth
     */
    public static function getInstance()
    {
        if (null === self::$_instance) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }
    public function getEntityManager()
    {
        return $this->_entityManager;
    }
    public function setEntityManager(EntityManager $entityManager)
    {
        $this->_entityManager = $entityManager;
    }
    public function getIdentity()
    {
        $storage = $this->getStorage();
        if ($storage->isEmpty()) {
            return null;
        }
        $identity = $storage->read();
        $em = $this->getEntityManager();
        if(UnitOfWork::STATE_DETACHED === $em->getUnitOfWork()->getEntityState($identity))
        {
            $identity = $em->merge($identity);
        }
        return $identity;
    }
}

然后添加一个_init函数到你的Bootstrap:

public function _initAuth()
{
    $this->bootstrap('doctrine');
    $em = $this->getResource('doctrine')->getEntityManager();
    $auth = My_Auth::getInstance();
    $auth->setEntityManager($em);
}

此时调用$user->getUserGroup()->getName();应该可以正常工作。

当您将实体存储到会话(通过Zend_Auth或其他方式)时,对象被序列化,并且在随后检索和反序列化时不再由Doctrine维护。尝试将实体合并回EntityManager。见http://www.doctrine-project.org/docs/orm/2.1/en/reference/working-with-objects.html