Doctrine使用代理对象来表示相关对象,以便于延迟加载。这是一个非常酷的功能,但它导致我试图完成的事情的问题。
我已经定制了我的用户对象,因此它们都需要与不同的对象相关联,我称之为城市。这种关系很好。
我有一个表单,我的用户填写以生成另一个对象,街道。街道也与城市对象有关。我没有让用户在填写表单时选择城市,而是在将对象持久保存到数据库之前自动设置它。
我尝试使用$event->setCity($user->getCity())
,但由于$ user-> getCity()返回一个代理对象,因此会产生错误。有没有我可以从代理对象调用的函数来获取真实的函数?
注意:我知道我可以创建一个带有连接的自定义查询来强制学说实际加载相关对象,但由于这是用户(使用FOSUserBundle),这将很难正确执行。
编辑:正如@flu所提到的,这种方法不会返回“true”对象。但是,如果您需要来自对象的数据,它可能很有用。然后,您可以通过一些标识从ObjectManager获取真实对象。
我们可以使用Proxy接口的__load()方法
$proxyObject->__load();
这不太可能对问题的特定实例有所帮助,因为您依赖于第三方模块,但您可以通过将实体的“获取模式”设置为“EAGER”来阻止延迟加载。
User:
ManyToOne:
city:
fetch: EAGER
这也可以通过注释来处理:
@ManyToOne(targetEntity="city", fetch="EAGER")
@JoinColumn(name="city", referencedColumnName="id")
我在这里看到的其他答案都没有为我工作。
这是我的解决方案:
我所有的实体都有id
属性和getId()
方法
$em = $this->getDoctrine()->getManager();
// 1 -> get the proxy object class name
$proxy_class_name = get_class($proxyObject);
// 2 -> get the real object class name
$class_name = $em->getClassMetadata($proxy_class_name)->rootEntityName;
// 3 -> get the real object
$object = $em->find($class_name, $proxyObject->getId());
如果id
属性和getId()
方法在Trait
类中,此解决方案不起作用
我希望它可以帮助某人
您从Doctrine获得实体的唯一实例。如果您要求两次,您将始终获得相同的对象。
因此,如果您的实体首先是延迟加载的(例如,通过某处的@ManyToOne),则此实体实例将是代理。
例:
您有一个在Config实体上具有双向@OneToOne
的用户实体...
情况1
您要求您的用户:
如果您稍后在应用程序的任何部分中请求相同的Config实体,则最终会使用该代理。
案例2
您请求您的配置,之前从未导入过您的用户:
如果您稍后在应用程序的任何部分中请求相同的用户实体,则最终会使用该代理。
总而言之,再次查询同一个实体仍将最终成为代理(无论如何,这都是您的用户的实例,因为生成的代理从它扩展)。
如果你真的需要你的实体的第二个实例是一个real
(如果你的一些app逻辑执行get_class
你不能用instanceof
代替),你可以尝试使用$em->detach()
但它将是一场噩梦(因此你的应用程序可能表现得比Doctrine带来的更多魔法)。
一个解决方案(我假设来自我的黑暗面)可以手动重新创建一个非托管实体。
public function getRealEntity($proxy)
{
if ($proxy instanceof Doctrine\ORM\Proxy\Proxy) {
$metadata = $this->getManager()->getMetadataFactory()->getMetadataFor(get_class($proxy));
$class = $metadata->getName();
$entity = new $class();
$reflectionSourceClass = new \ReflectionClass($proxy);
$reflectionTargetClass = new \ReflectionClass($entity);
foreach ($metadata->getFieldNames() as $fieldName) {
$reflectionPropertySource = $reflectionSourceClass->getProperty($fieldName);
$reflectionPropertySource->setAccessible(true);
$reflectionPropertyTarget = $reflectionTargetClass->getProperty($fieldName);
$reflectionPropertyTarget->setAccessible(true);
$reflectionPropertyTarget->setValue($entity, $reflectionPropertySource->getValue($proxy));
}
return $entity;
}
return $proxy;
}
这是一个有点讨厌的解决方法:
// $proxyObject = ...
$em->detach($proxyObject);
$entityObject = $em->find(<ENTITY_CLASS>, $proxyObject->getId());
// now you have real entity and not the proxy (entityObject instead of proxyObject)
之后,如果需要将代理引用放在其他实体中,则可以替换代理引用
Doctrines延迟加载非常适合它的工作,并且只要你尝试使用它或它的任何属性就会用一个代理对象替换它。如果由于代理对象而遇到问题(就像我在我的问题中所做的那样),您的模型中很可能出现错误。
也就是说,你可以告诉学说通过告诉它“保湿”来提取所有相关数据:$query->getResult(Doctrine\ORM\Query::HYDRATE_ARRAY);
Symfony PropertyNormalizer
使用ReflectionClass
getParent
方法绕过这个问题。如果您需要检查对象,就像使用标准化程序一样,而不是仅仅使用->get
方法,您应该能够使用类似的方法。
似乎Proxy具有实际实体的父级,这就是instanceof仍然有效的原因。
我找到的解决方案是扩展Reflection Hydrator
并将新类用作Hal提取器参数,用于复杂实体。
你可以在这里找到代码:
Synergy of proxy object of doctrine and zend expressive hal
大概。
这将将引用转换为真实对象并获取所有字段。
$entityManager->refresh($proxyObject);