40
votes

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.

11
I think this is supposed to work, even if $user->getCity() returns a proxy object, because proxy objects should behave just like true objects. Could you show us the error you're getting?greg0ire
As gregOire says, the proxy object inherits from the city object. What kind of error are you receiving? When Doctrine persists relations these are always proxy objects.Kees Schepers
Just noticed my issue, I got the namespace of my city class wrong in my entity class. Thanks for the help.MrGlass

11 Answers

20
votes

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.

11
votes

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

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

7
votes

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;
}
2
votes

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

1
votes

@mustaccio, @Heather Orr

I had this problem. Thanks for idea,

This is the code:

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

Another way, if you just need to read annotations:

// $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)
}

Not absolutely related to original question, but I hope this will help some people...

0
votes

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

0
votes

The Symfony PropertyNormalizer gets around this issue using the ReflectionClass getParent method. If you need to inspect the object, as for a normalizer, rather than just use the ->get methods, you should be able to use a similar approach.

It appears the Proxy has a parent of the actual entity, which is why instanceof still works.

0
votes

The solution I found is to extend the Reflection Hydrator and use the new class as Hal extractor params, for complex entities.

You can find the code here:

Synergy of proxy object of doctrine and zend expressive hal

probably.

0
votes

If you first used $em->getReference and then $em->find, then the result will be a _proxy. I recommend using:

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

This will both convert the reference to a real object and fetch all the fields.

$entityManager->refresh($proxyObject);