web-dev-qa-db-fra.com

Comment encoder des entités Doctrine en JSON dans Symfony 2.0 AJAX??

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 ..,

87
Dmytro Krasun

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;
80
Sofia

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

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');
39
webda2l

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

11
jerome

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.

10
rkmax

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

} 
6
oxy

dans Symfony 2.3

/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.

6
Lebnik

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

6
Julien Fastré

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

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 -

  1. 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()]);
    }
    
  2. 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 -

4
Anis

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:

  1. JMSSerializerBundle pour la sérialisation des entités Doctrine
  2. FOSRestBundle bundle pour l'écouteur de vue de réponse. En outre, il peut générer une définition des itinéraires en fonction du nom du contrôleur/de l'action.
  3. NelmioApiDocBundle pour générer automatiquement la documentation en ligne et Sandbox (qui permet de tester le terminal sans outil externe).

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:

  • Les annotations @JMS\Expose () de l'entité peuvent être ajoutées à des champs simples et à tout type de relations. Il est également possible d'exposer le résultat de l'exécution d'une méthode (utilisez l'annotation @JMS\VirtualProperty () pour cela)
  • Avec les groupes de sérialisation, nous pouvons contrôler les champs exposés dans différentes situations.
  • Les contrôleurs sont très simples. La méthode d'action peut renvoyer directement une entité ou un tableau d'entités, qui seront automatiquement sérialisées.
  • Et @ApiDoc () permet de tester le noeud final directement à partir du navigateur, sans aucun REST client ou code JavaScript
4
Maksym Moskvychev

Maintenant, vous pouvez aussi utiliser Doctrine ORM Transformations pour convertir des entités en tableaux imbriqués de scalaires et inversement

2
ScorpioT1000