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

Posted

技术标签:

【中文标题】Doctrine 不能正确地从会话中加载关联【英文标题】:Doctrine doesn't load associations from session correctly 【发布时间】: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 吗?有什么建议吗?

【问题讨论】:

【参考方案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 实体的东西。希望你能理解。

以上是关于Doctrine 不能正确地从会话中加载关联的主要内容,如果未能解决你的问题,请参考以下文章

数据不会不断地从自定义 wordpress 表中加载

在不同的会话中加载 TensorFlow 模型

如何将数据持久化(转储)到本地存储并在以后的会话中加载?

在 XTemplate 中加载关联的模型数据

如何使用 sequelize.js 从关联模型中加载属性

NX二次开发-UFUN获取当前会话中加载的部件数量UF_PART_ask_num_parts