Getting a “true” object from a proxy object in doc

2020-05-19 20:59发布

Doctrine uses proxy objects to represent related objects in order to facilitate lazy loading. This is a really cool feature, but its causing an issue with something I am trying to accomplish.

I have customized my user object so that they are all required to be related to a different object, which I will call city. This relationship is working fine.

I have a form that my user fills out to generate another object, street. Street is also related to the city object. Instead of having my user select the city when they fill out the form, I want to automatically set it before I persist the object to my database.

I tried using $event->setCity($user->getCity()), but since $user->getCity() returns a proxy object, this generates an error. Is there a function I can call from the proxy object to get the real one?

Note: I am aware I can create a custom query with a join to force doctrine to actually load the related object, but since this is the user (using FOSUserBundle) that would be difficult to do properly.

10条回答
We Are One
2楼-- · 2020-05-19 21:14

This is unlikely to help in the specific instance for the question, since you're relying on a third-party module, but you can prevent the lazy loading by setting the "fetch mode" for your entity to "EAGER".

User:
    ManyToOne:
        city:
            fetch: EAGER

This can also be handled by annotations:

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

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

None of the other answers I've seen here worked for me.

查看更多
我只想做你的唯一
3楼-- · 2020-05-19 21:16

Edit: As mention by @flu this approach don't return the "true" object. However it can be useful in case if you need the data from the object. Then, you can just get the real object from ObjectManager by some of identity.


We can use __load() method from Proxy interface

$proxyObject->__load();
查看更多
Bombasti
4楼-- · 2020-05-19 21:16

You get an unique instance of an entity from Doctrine. If you request it two times, you'll always get the same object.

As a consequence, if your entity is lazy-loaded first (via a @ManyToOne somewhere for example), this entity instance will be a Proxy.

Example:

You have a User entity having a bidirectional @OneToOne on a Config entity...

Case 1

You request your User:

  • you get a real instance of User
  • $user->config will contain a Proxy

If later on you request the same Config entity in any piece of your app, you'll end up with that proxy.

Case 2

You request your Config and your User has never been imported before:

  • you get a real instance of Config
  • $config->user will contain a Proxy

If later on you request the same User entity in any piece of your app, you'll end up with that proxy.


All in all, querying again for the same entity will still end up to a proxy (which is an instance of your User anyway, because the generated proxy extends from it).

If you really need a second instance of your entity that is a real one (if some of your app logic does a get_class that you can't replace by instanceof for example), you can try to play with $em->detach() but it will be a nightmare (and thus your app may behave with even more magic than Doctrine already bring).

A solution (coming from my dark side, I assume) can be recreating a non-managed Entity manually.

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;
}
查看更多
Emotional °昔
5楼-- · 2020-05-19 21:16

Doctrines lazy loading is very good at its job, and will replace a proxy object with a real one as soon as you try to use it or any of its properties. If you are having issues due to the proxy objects (like I did in my question) you most probably have an error in your model.

That said, you can tell doctrine to pull all the related data by telling it to "hydrate": $query->getResult(Doctrine\ORM\Query::HYDRATE_ARRAY);

查看更多
Viruses.
6楼-- · 2020-05-19 21:18

Here is my solution:

Context:

All my entities have id property and getId() method


Solution:

$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());

Problem:

This solution don't work if id property and getId() method are in a Trait class

I hope that it can help someone

查看更多
SAY GOODBYE
7楼-- · 2020-05-19 21:20

This is a little bit nasty workaround that issue :

// $proxyObject = ...

$em->detach($proxyObject);
$entityObject = $em->find(<ENTITY_CLASS>, $proxyObject->getId());

// now you have real entity and not the proxy (entityObject instead of proxyObject)

after that you can replace proxy reference if you need to have it inside other entities

查看更多
登录 后发表回答