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 不能正确地从会话中加载关联的主要内容,如果未能解决你的问题,请参考以下文章