【问题标题】:Getting a "true" object from a proxy object in doctrine2从教义2中的代理对象获取“真实”对象
【发布时间】:2012-01-10 09:15:53
【问题描述】:

Doctrine 使用代理对象来表示相关对象,以便于延迟加载。这是一个非常酷的功能,但它会导致我正在尝试完成的事情出现问题。

我已经自定义了我的用户对象,因此它们都需要与不同的对象相关联,我将其称为城市。这种关系运作良好。

我有一个表单,我的用户填写它以生成另一个对象,街道。街道也与城市对象有关。我不想让我的用户在填写表单时选择城市,而是希望在将对象保存到我的数据库之前自动设置它。

我尝试使用$event->setCity($user->getCity()),但由于 $user->getCity() 返回一个代理对象,这会产生错误。有没有我可以从代理对象调用的函数来获取真正的函数?

注意:我知道我可以创建一个带有连接的自定义查询,以强制学说实际加载相关对象,但由于这是用户(使用 FOSUserBundle),因此很难正确执行。 em>

【问题讨论】:

  • 我认为这应该可以工作,即使$user->getCity() 返回一个代理对象,因为代理对象应该像真正的对象一样表现。你能告诉我们你遇到的错误吗?
  • 正如gregOire 所说,代理对象继承自城市对象。你收到什么样的错误?当 Doctrine 保持关系时,这些总是代理对象。
  • 刚刚注意到我的问题,我的实体类中的城市类的命名空间错误。感谢您的帮助。

标签: symfony doctrine-orm


【解决方案1】:

@mustaccio,@Heather Orr

我遇到了这个问题。感谢您的想法,

这是代码:

// $object is a Proxy class
$objectClass = get_class($object);
$reflectionClass = new \ReflectionClass($objectClass);

// Make sure we are not using a Proxy class
if ($obj instanceof Proxy) {
    $reflClass = $reflClass->getParentClass();
}

另一种方式,如果你只需要阅读注释:

// $object is a Proxy class
$objectClass = get_class($object);
$classMetadata = $this->em->getClassMetadata($objectClass);

foreach ($classMetadata->getReflectionProperties() as $property) {
    $annotation = $this->annotationReader->getPropertyAnnotation($property, YOUR_ANNOTATION::class)
}

与原始问题没有绝对关系,但我希望这会对某些人有所帮助......

【讨论】:

    【解决方案2】:

    如果您先使用$em->getReference,然后使用$em->find,则结果将是_proxy。 我建议使用:

     createQueryBuilder ... ->getQuery()
        ->setHint (Query::HINT_FORCE_PARTIAL_LOAD, true)
        ->getOneOrNullResult();
    

    【讨论】:

      【解决方案3】:

      我找到的解决方案是扩展 Reflection Hydrator 并将新类用作 Hal 提取器参数,用于复杂实体。

      你可以在这里找到代码:

      Synergy of proxy object of doctrine and zend expressive hal

      大概吧。

      【讨论】:

        【解决方案4】:

        Symfony PropertyNormalizer 使用 ReflectionClass getParent 方法解决了这个问题。如果您需要检查对象,就像规范化器一样,而不仅仅是使用->get 方法,您应该能够使用类似的方法。

        看起来 Proxy 有一个实际实体的父级,这就是 instanceof 仍然有效的原因。

        【讨论】:

          【解决方案5】:

          编辑: 正如@flu 提到的,这种方法不会返回“真实”对象。但是,如果您需要来自对象的数据,它会很有用。 然后,您可以通过一些身份从 ObjectManager 中获取真实对象。


          我们可以使用 Proxy 接口中的 __load() 方法

          $proxyObject->__load();
          

          【讨论】:

          • 虽然它可能有用,但这并不能回答 OP 的问题,因为 Proxy::__load() 返回 void 并且 $proxyObject 在调用 $proxyObject->__load(); 后仍然是代理所以这不会返回true 来自代理的对象。
          • 它没有得到真正的对象。它只是加载所有实体值。
          【解决方案6】:

          这是一个有点讨厌的解决方法:

          // $proxyObject = ...
          
          $em->detach($proxyObject);
          $entityObject = $em->find(<ENTITY_CLASS>, $proxyObject->getId());
          
          // now you have real entity and not the proxy (entityObject instead of proxyObject)
          

          之后,如果您需要在其他实体中使用代理引用,您可以替换它

          【讨论】:

            【解决方案7】:

            您从 Doctrine 获得一个实体的唯一实例。如果您请求两次,您将始终获得相同的对象。

            因此,如果您的实体首先被延迟加载(例如通过 @ManyToOne 某处),则此实体实例将是一个代理。

            例子:

            您的用户实体在配置实体上具有双向 @OneToOne...

            案例 1

            您要求您的用户:

            • 你得到一个真实的用户实例
            • $user->config 将包含一个代理

            如果稍后您在应用的任何部分中请求相同的 Config 实体,您最终会得到该代理。

            案例 2

            您请求您的配置并且您的用户之前从未被导入:

            • 你得到一个真实的 Config实例
            • $config->user 将包含一个代理

            如果稍后您在应用的任何部分中请求相同的用户实体,您最终将使用该代理。


            总而言之,再次查询同一个实体最终仍将是一个代理(无论如何它是您的用户的一个实例,因为生成的代理是从它扩展而来的)。

            如果您真的需要您的实体的第二个实例,它是 real 一个(如果您的某些应用程序逻辑执行 get_class,您无法用 instanceof 替换例如),您可以尝试使用$em-&gt;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;
            }
            

            【讨论】:

            • 恕我直言,使用 ClassMetadata::getFieldValueClassMetadata::setFieldValue 会简化您的 getRealEntity
            【解决方案8】:

            由于您依赖第三方模块,这不太可能对问题的特定实例有所帮助,但您可以通过将实体的“获取模式”设置为“EAGER”来防止延迟加载。

            User:
                ManyToOne:
                    city:
                        fetch: EAGER
            

            这也可以通过注解来处理:

            @ManyToOne(targetEntity="city", fetch="EAGER")
            @JoinColumn(name="city", referencedColumnName="id")
            

            http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#annref-manytoone

            我在这里看到的其他答案都不适合我。

            【讨论】:

            • 这解决了'脚本试图执行方法或访问不完整对象的属性',当在_SESSION中保存实例时
            【解决方案9】:

            这是我的解决方案:

            上下文:

            所有我的实体都有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 类中,则此解决方案不起作用

            希望对大家有所帮助

            【讨论】:

              【解决方案10】:

              这会将引用转换为真实对象并获取所有字段。

              $entityManager->refresh($proxyObject);
              

              【讨论】:

              • $this-&gt;getEntityManager()-&gt;refresh($object); return get_class($object); 仍然显示代理。
              【解决方案11】:

              Doctrines 延迟加载非常擅长它的工作,一旦你尝试使用它或它的任何属性,它就会用一个真实的代理对象替换它。如果您因代理对象而遇到问题(就像我在问题中所做的那样),您的模型很可能有错误。

              也就是说,您可以告诉教条通过“水合”来提取所有相关数据: $query-&gt;getResult(Doctrine\ORM\Query::HYDRATE_ARRAY);

              【讨论】:

                猜你喜欢
                • 2019-01-29
                • 2020-04-04
                • 2017-06-16
                • 1970-01-01
                • 2014-11-04
                • 2011-11-20
                • 1970-01-01
                • 2022-01-10
                • 1970-01-01
                相关资源
                最近更新 更多