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.
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();
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")
Aucune des autres réponses que j'ai vues ici n'a fonctionné pour moi.
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:
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:
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;
}
Voici ma solution:
Tous mes entités ont la propriété id
et la méthode 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());
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
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
Symfony PropertyNormalizer
résout ce problème en utilisant la méthode ReflectionClass
getParent
. 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.
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);