Je développe une application de jeu et j'utilise Symfony 2.0. J'ai beaucoup de AJAX requêtes au serveur. Et plus de réponses convertit une entité en JSON. Par exemple:
class DefaultController extends Controller
{
public function launchAction()
{
$user = $this->getDoctrine()
->getRepository('UserBundle:User')
->find($id);
// encode user to json format
$userDataAsJson = $this->encodeUserDataToJson($user);
return array(
'userDataAsJson' => $userDataAsJson
);
}
private function encodeUserDataToJson(User $user)
{
$userData = array(
'id' => $user->getId(),
'profile' => array(
'nickname' => $user->getProfile()->getNickname()
)
);
$jsonEncoder = new JsonEncoder();
return $jsonEncoder->encode($userData, $format = 'json');
}
}
Et tous mes contrôleurs font la même chose: obtenir une entité et encoder certains de ses champs en JSON. Je sais que je peux utiliser des normalisateurs et encoder toutes les entités. Mais que se passe-t-il si une entité a cyclé des liens vers une autre entité? Ou le graphe d'entités est très grand? Avez-vous des suggestions?
Je pense à un schéma d'encodage pour les entités ... ou à l'utilisation de NormalizableInterface
pour éviter le cycle ..,
Une autre option consiste à utiliser le JMSSerializerBundle . Dans votre contrôleur, vous faites alors
$serializer = $this->container->get('serializer');
$reports = $serializer->serialize($doctrineobject, 'json');
return new Response($reports); // should be $reports as $doctrineobject is not serialized
Vous pouvez configurer le mode de réalisation de la sérialisation en utilisant des annotations dans la classe d'entité. Voir la documentation dans le lien ci-dessus. Par exemple, voici comment vous excluriez les entités liées:
/**
* Iddp\RorBundle\Entity\Report
*
* @ORM\Table()
* @ORM\Entity(repositoryClass="Iddp\RorBundle\Entity\ReportRepository")
* @ExclusionPolicy("None")
*/
....
/**
* @ORM\ManyToOne(targetEntity="Client", inversedBy="reports")
* @ORM\JoinColumn(name="client_id", referencedColumnName="id")
* @Exclude
*/
protected $client;
Avec php5.4 maintenant, vous pouvez faire:
use JsonSerializable;
/**
* @Entity(repositoryClass="App\Entity\User")
* @Table(name="user")
*/
class MyUserEntity implements JsonSerializable
{
/** @Column(length=50) */
private $name;
/** @Column(length=50) */
private $login;
public function jsonSerialize()
{
return array(
'name' => $this->name,
'login'=> $this->login,
);
}
}
Et puis appelez
json_encode(MyUserEntity);
Vous pouvez encoder automatiquement en Json, votre entité complexe avec:
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
$serializer = new Serializer(array(new GetSetMethodNormalizer()), array('json' => new
JsonEncoder()));
$json = $serializer->serialize($entity, 'json');
Pour compléter la réponse: Symfony2 est livré avec un wrapper autour de json_encode: Symfony/Component/HttpFoundation/JsonResponse
Utilisation typique dans vos contrôleurs:
...
use Symfony\Component\HttpFoundation\JsonResponse;
...
public function acmeAction() {
...
return new JsonResponse($array);
}
J'espère que cela t'aides
J
J'ai trouvé la solution au problème de la sérialisation des entités était la suivante:
#config/config.yml
services:
serializer.method:
class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
serializer.encoder.json:
class: Symfony\Component\Serializer\Encoder\JsonEncoder
serializer:
class: Symfony\Component\Serializer\Serializer
arguments:
- [@serializer.method]
- {json: @serializer.encoder.json }
dans mon contrôleur:
$serializer = $this->get('serializer');
$entity = $this->get('doctrine')
->getRepository('myBundle:Entity')
->findOneBy($params);
$collection = $this->get('doctrine')
->getRepository('myBundle:Entity')
->findBy($params);
$toEncode = array(
'response' => array(
'entity' => $serializer->normalize($entity),
'entities' => $serializer->normalize($collection)
),
);
return new Response(json_encode($toEncode));
autre exemple:
$serializer = $this->get('serializer');
$collection = $this->get('doctrine')
->getRepository('myBundle:Entity')
->findBy($params);
$json = $serializer->serialize($collection, 'json');
return new Response($json);
vous pouvez même le configurer pour désérialiser les tableaux dans http://api.symfony.com/2.
Il me suffisait de résoudre le même problème: coder en json une entité ("utilisateur") ayant une association bidirectionnelle un-à-plusieurs avec une autre entité ("emplacement").
J'ai essayé plusieurs choses et je pense avoir trouvé la meilleure solution acceptable. L'idée était d'utiliser le même code que celui écrit par David, mais d'intercepter la récursion infinie en disant au Normalizer de s'arrêter à un moment donné.
Je ne voulais pas mettre en place un normalisateur personnalisé, car ce GetSetMethodNormalizer est une approche de Nice à mon avis (basée sur la réflexion, etc.). J'ai donc décidé de le sous-classer, ce qui n'est pas trivial à première vue, car la méthode pour indiquer si inclure une propriété (isGetMethod) est privée.
Mais, comme on peut remplacer la méthode normalize, j’ai intercepté à ce stade, en désactivant simplement la propriété qui fait référence à "Location" - ainsi la boucle inifinite est interrompue.
Dans le code cela ressemble à ceci:
class GetSetMethodNormalizer extends \Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer {
public function normalize($object, $format = null)
{
// if the object is a User, unset location for normalization, without touching the original object
if($object instanceof \Leonex\MoveBundle\Entity\User) {
$object = clone $object;
$object->setLocations(new \Doctrine\Common\Collections\ArrayCollection());
}
return parent::normalize($object, $format);
}
}
/app/config/config.yml
framework:
# сервис конвертирования объектов в массивы, json, xml и обратно
serializer:
enabled: true
services:
object_normalizer:
class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
tags:
# помечаем к чему относится этот сервис, это оч. важно, т.к. иначе работать не будет
- { name: serializer.normalizer }
et exemple pour votre contrôleur:
/**
* Поиск сущности по ИД объекта и ИД языка
* @Route("/search/", name="orgunitSearch")
*/
public function orgunitSearchAction()
{
$array = $this->get('request')->query->all();
$entity = $this->getDoctrine()
->getRepository('IntranetOrgunitBundle:Orgunit')
->findOneBy($array);
$serializer = $this->get('serializer');
//$json = $serializer->serialize($entity, 'json');
$array = $serializer->normalize($entity);
return new JsonResponse( $array );
}
mais les problèmes avec le type de champ\DateTime resteront.
J'ai eu le même problème et j'ai choisi de créer mon propre codeur, qui se débrouillera tout seul avec la récursivité.
J'ai créé des classes qui implémentent Symfony\Component\Serializer\Normalizer\NormalizerInterface
, et un service qui contient chaque NormalizerInterface
.
#This is the NormalizerService
class NormalizerService
{
//normalizer are stored in private properties
private $entityOneNormalizer;
private $entityTwoNormalizer;
public function getEntityOneNormalizer()
{
//Normalizer are created only if needed
if ($this->entityOneNormalizer == null)
$this->entityOneNormalizer = new EntityOneNormalizer($this); //every normalizer keep a reference to this service
return $this->entityOneNormalizer;
}
//create a function for each normalizer
//the serializer service will also serialize the entities
//(i found it easier, but you don't really need it)
public function serialize($objects, $format)
{
$serializer = new Serializer(
array(
$this->getEntityOneNormalizer(),
$this->getEntityTwoNormalizer()
),
array($format => $encoder) );
return $serializer->serialize($response, $format);
}
Un exemple de normalisateur:
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class PlaceNormalizer implements NormalizerInterface {
private $normalizerService;
public function __construct($normalizerService)
{
$this->service = normalizerService;
}
public function normalize($object, $format = null) {
$entityTwo = $object->getEntityTwo();
$entityTwoNormalizer = $this->service->getEntityTwoNormalizer();
return array(
'param' => object->getParam(),
//repeat for every parameter
//!!!! this is where the entityOneNormalizer dealt with recursivity
'entityTwo' => $entityTwoNormalizer->normalize($entityTwo, $format.'_without_any_entity_one') //the 'format' parameter is adapted for ignoring entity one - this may be done with different ways (a specific method, etc.)
);
}
}
Dans un contrôleur:
$normalizerService = $this->get('normalizer.service'); //you will have to configure services.yml
$json = $normalizerService->serialize($myobject, 'json');
return new Response($json);
Le code complet est ici: https://github.com/progracqteur/WikiPedale/tree/master/src/Progracqteur/WikipedaleBundle/Resources/Normalizer
Il s’agit plus d’une mise à jour (pour Symfony v: 2.7+ et JmsSerializer v: 0.13. * @ Dev), afin d’éviter que Jms essaie de charger et de sérialiser le graphe d’objet entier (ou en cas de relation ..)
Modèle:
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation\ExclusionPolicy;
use JMS\Serializer\Annotation\Exclude;
use JMS\Serializer\Annotation\MaxDepth; /* <=== Required */
/**
* User
*
* @ORM\Table(name="user_table")
///////////////// OTHER Doctrine proprieties //////////////
*/
public class User
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\ManyToOne(targetEntity="FooBundle\Entity\Game")
* @ORM\JoinColumn(nullable=false)
* @MaxDepth(1)
*/
protected $game;
/*
Other proprieties ....and Getters ans setters
......................
......................
*/
À l'intérieur d'une action:
use JMS\Serializer\SerializationContext;
/* Necessary include to enbale max depth */
$users = $this
->getDoctrine()
->getManager()
->getRepository("FooBundle:User")
->findAll();
$serializer = $this->container->get('jms_serializer');
$jsonContent = $serializer
->serialize(
$users,
'json',
SerializationContext::create()
->enableMaxDepthChecks()
);
return new Response($jsonContent);
Si vous utilisez Symfony 2.7 ou version ultérieure et que vous ne voulez pas inclure de paquet supplémentaire pour la sérialisation, vous pouvez peut-être suivre cette méthode pour seialize doctrine entités à json -
Dans mon contrôleur (commun, parent), j'ai une fonction qui prépare le sérialiseur
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
// -----------------------------
/**
* @return Serializer
*/
protected function _getSerializer()
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizer = new ObjectNormalizer($classMetadataFactory);
return new Serializer([$normalizer], [new JsonEncoder()]);
}
Puis utilisez-le pour sérialiser les entités en JSON
$this->_getSerializer()->normalize($anEntity, 'json');
$this->_getSerializer()->normalize($arrayOfEntities, 'json');
Terminé!
Mais vous aurez peut-être besoin d'une mise au point. Par exemple -
Lorsque vous devez créer de nombreux REST points de terminaison d'API sur Symfony, le meilleur moyen consiste à utiliser la pile d'ensembles suivante:
Lorsque vous configurez tout correctement, votre code d'entité ressemblera à:
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* @ORM\Table(name="company")
*/
class Company
{
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=255)
*
* @JMS\Expose()
* @JMS\SerializedName("name")
* @JMS\Groups({"company_overview"})
*/
private $name;
/**
* @var Campaign[]
*
* @ORM\OneToMany(targetEntity="Campaign", mappedBy="company")
*
* @JMS\Expose()
* @JMS\SerializedName("campaigns")
* @JMS\Groups({"campaign_overview"})
*/
private $campaigns;
}
Ensuite, code dans le contrôleur:
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use FOS\RestBundle\Controller\Annotations\View;
class CompanyController extends Controller
{
/**
* Retrieve all companies
*
* @View(serializerGroups={"company_overview"})
* @ApiDoc()
*
* @return Company[]
*/
public function cgetAction()
{
return $this->getDoctrine()->getRepository(Company::class)->findAll();
}
}
Les avantages d'une telle installation sont les suivants:
Maintenant, vous pouvez aussi utiliser Doctrine ORM Transformations pour convertir des entités en tableaux imbriqués de scalaires et inversement