Doctrine不会正确加载会话中的关联

时间:2011-11-06 18:06:49

标签: php orm doctrine-orm

我在Doctrine 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;
}

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;

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

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

2 个答案:

答案 0 :(得分:3)

您的问题是mjh_ca回答的内容与AbstractEntity实施问题的结合。

由于您显示以这种方式访问​​实体字段:

$user->userGroup->name;

我认为您的AbstractEntity基类使用__get()__set()魔术方法而不是正确的getter和setter:

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

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

你基本上打破了延迟加载:

  

“......无论何时访问尚未初始化的代理对象的公共属性,返回值都将为null.Doctrine无法挂钩到此进程并神奇地使实体延迟加载。”

来源:Doctrine Best Practices: Don't Use Public Properties on Entities

您应该以这种方式访问​​字段:

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

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

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

问题是,如何干净利落地做到这一点。您可以考虑为__wakeup()实体实施User魔术方法,但这也违反了学说最佳做法......

来源:Implementing Wakeup or Clone

由于我们讨论的是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;
    }
}

然后在你的Bootstrap中添加_init函数:

public function _initAuth()
{
    $this->bootstrap('doctrine');
    $em = $this->getResource('doctrine')->getEntityManager();

    $auth = My_Auth::getInstance();
    $auth->setEntityManager($em);
}

此时调用$user->getUserGroup()->getName();应按预期工作。

答案 1 :(得分:1)

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