web-dev-qa-db-fra.com

Obtenir un "vrai" objet à partir d'un objet proxy dans doctrine2

Doctrine utilise des objets proxy pour représenter des objets liés afin de faciliter le chargement paresseux. C'est une fonctionnalité vraiment géniale, mais elle cause un problème avec quelque chose que j'essaie d'accomplir.

J'ai personnalisé mon objet utilisateur pour qu'il soit lié à un autre objet, que j'appellerai city. Cette relation fonctionne bien.

J'ai un formulaire que mon utilisateur remplit pour générer un autre objet, street. La rue est également liée à l'objet de la ville. Au lieu que mon utilisateur sélectionne la ville lorsqu'il remplit le formulaire, je souhaite le définir automatiquement avant de conserver l'objet dans ma base de données.

J'ai essayé d'utiliser $event->setCity($user->getCity()), mais puisque $ user-> getCity () retourne un objet proxy, cela génère une erreur. Existe-t-il une fonction que je peux appeler depuis l'objet proxy pour obtenir le vrai?

Remarque: Je sais que je peux créer une requête personnalisée avec une jointure pour forcer la doctrine à charger réellement l'objet associé, mais il s'agit d'un utilisateur (en utilisant FOSUserBundle) difficile à exécuter correctement.

32
MrGlass

Edit: Comme mentionné par @flu, cette approche ne renvoie pas l'objet "true". Cependant, cela peut être utile si vous avez besoin des données de l'objet. Ensuite, vous pouvez simplement obtenir le véritable objet à partir de ObjectManager par une partie de son identité.


Nous pouvons utiliser la méthode __load () de l'interface proxy

$proxyObject->__load();
13
Serhii Polishchuk

Il est peu probable que cela aide dans l'instance spécifique à la question, puisque vous utilisez un module tiers, mais vous pouvez empêcher le chargement paresseux en définissant le "mode de récupération" de votre entité sur "EAGER".

User:
    ManyToOne:
        city:
            fetch: EAGER

Cela peut aussi être traité par des annotations:

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

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

Aucune des autres réponses que j'ai vues ici n'a fonctionné pour moi.

14
beltouche

Vous obtenez une instance unique d'une entité de Doctrine. Si vous le demandez deux fois, vous obtiendrez toujours le même objet.

En conséquence, si votre entité est d'abord chargée paresseuse (via un @ManyToOne quelque part, par exemple), cette instance d'entité sera un proxy.

Exemple:

Vous avez une entité utilisateur ayant un @OneToOne bidirectionnel sur une entité de configuration ...

Cas 1

Vous demandez à votre utilisateur:

  • vous obtenez une real instance de l'utilisateur
  • $ user-> config contiendra un proxy 

Si plus tard, vous demandez la même entité de configuration dans un élément de votre application, vous obtiendrez ce proxy.

Cas 2

Vous demandez votre configuration et votre utilisateur n'a jamais été importé auparavant:

  • vous obtenez une real instance de Config
  • $ config-> utilisateur contiendra un proxy

Si plus tard, vous demandez la même entité utilisateur dans un élément de votre application, vous obtiendrez ce proxy.


Dans l’ensemble, interroger à nouveau la même entité aboutira toujours à un proxy (qui est de toute façon une instance de votre utilisateur, car le proxy généré s’étend de celle-ci).

Si vous vraiment avez besoin d'une seconde instance de votre entité qui est une real (si une partie de la logique de votre application utilise un get_class que vous ne pouvez pas remplacer par instanceof par exemple), vous pouvez essayer de jouer avec $em->detach() mais sera un cauchemar (et donc votre application peut se comporter avec encore plus de magie que Doctrine en apportent déjà).

Une solution (venant de mon côté obscur, je suppose) peut être de recréer manuellement une entité non gérée.

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
Alain Tiemblo

Voici ma solution:

Le contexte:

Tous mes entités ont la propriété id et la méthode getId()


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

Problème:

Cette solution ne fonctionne pas si la propriété id et la méthode getId() sont dans une classe Trait

J'espère que ça peut aider quelqu'un

2
doydoy44

Voici une solution de contournement un peu désagréable:

// $proxyObject = ...

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

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

après cela, vous pouvez remplacer la référence du proxy si vous en avez besoin à l'intérieur d'autres entités 

1
Nikola Loncar

Symfony PropertyNormalizer résout ce problème en utilisant la méthode ReflectionClassgetParent . Si vous devez inspecter l'objet, comme pour un normalisateur, plutôt que d'utiliser simplement les méthodes ->get, vous devriez pouvoir utiliser une approche similaire.

Il semble que le proxy ait un parent de l'entité réelle, raison pour laquelle instanceof fonctionne toujours.

0
Heather Orr

Le chargement paresseux de Doctrines est très efficace, et remplacera un objet proxy par un objet réel dès que vous essayez de l'utiliser ou de l'une de ses propriétés. Si vous rencontrez des problèmes en raison des objets proxy (comme je l'ai fait dans ma question), vous avez probablement une erreur dans votre modèle.

Cela dit, vous pouvez dire à la doctrine de tirer toutes les données connexes en lui disant de "s'hydrater": $query->getResult(Doctrine\ORM\Query::HYDRATE_ARRAY);

0
MrGlass