【问题标题】:Doctrine doesn't load associations from session correctlyDoctrine 不能正确地从会话中加载关联
【发布时间】:2011-11-06 18:06:48
【问题描述】:

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

还有一个用户组实体:

/**
 * @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 变量,则不会在下一页返回 NULL。

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

【问题讨论】:

    标签: php orm doctrine-orm


    【解决方案1】:

    您的问题是 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);
    

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

    来源: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;
        }
    }
    

    然后将_init 函数添加到您的引导程序:

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

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 我试过了,但实际问题是 userGroup 关联在序列化之前没有填充。这会导致即使您直接将 merge() 调用到 userGroup 对象本身,它也不会生效。当您在序列化之前不访问 userGroup 对象时会出现此问题,因为 Doctrine 仅在调用实体数据时才加载它。你明白我的问题吗?现在我有一个解决方法 echo $user->userGroup->name;但这不是一个好的。我正在寻找手动加载 userGroup 实体的东西。希望你能理解。
      猜你喜欢
      • 1970-01-01
      • 2014-09-30
      • 1970-01-01
      • 1970-01-01
      • 2014-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-10
      相关资源
      最近更新 更多