Comment encoder des entités de Doctrine à JSON dans L'application Symfony 2.0 AJAX?

je développe game app et utilise Symfony 2.0. J'ai beaucoup de requêtes AJAX au backend. Et plus de réponses convertit entity 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 à JSON. Je sais que je peux utiliser des normalisateurs et coder toutes les Entities. Mais que faire si une entité a parcouru des liens vers une autre entité? Ou les entités graphique est très grand? Avez-vous des suggestions?

je pense à un schéma d'encodage pour les entités... ou en utilisant NormalizableInterface pour éviter le vélo..,

85
demandé sur likeitlikeit 2011-07-15 15:41:32

12 réponses

une autre option est d'utiliser le JMSSerializerBundle . Dans votre controller 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 la façon dont la sérialisation est faite en utilisant des annotations dans la classe entity. Consultez 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;
79
répondu Sofia 2014-04-03 09:22:07

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

puis appelez

json_encode(MyUserEntity);
141
répondu SparSio 2012-07-04 08:23:37

vous pouvez encoder automatiquement dans 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
répondu webda2l 2012-05-31 03:06:41

pour compléter la réponse: Symfony2 est livré avec un wrapper autour de json_encode: Symfony / Component/HttpFoundation /JsonResponse

usage typique dans vos contrôleurs:

...
use Symfony\Component\HttpFoundation\JsonResponse;
...
public function acmeAction() {
...
return new JsonResponse($array);
}

Espérons que cette aide

J

11
répondu jerome 2013-07-09 15:38:15

j'ai trouvé la solution au problème des entités de sérialisation était comme suit:

#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.0

10
répondu rkmax 2011-12-21 17:04:39

j'ai juste eu à résoudre le même problème: JSON-encoder une entité ("utilisateur") ayant une Association bidirectionnelle un-à-plusieurs à une autre entité ("localisation").

j'ai essayé plusieurs choses et je pense que maintenant j'ai trouvé la meilleure solution acceptable. L'idée était d'utiliser le même code que celui écrit par David, mais d'une manière ou d'une autre intercepter la récursion infinie en disant au normalisateur de s'arrêter à un moment donné.

Je ne voulais pas mettre en œuvre un normalisateur personnalisé, comme ce Getsetmethodnormaliserest une approche agréable à mon avis (basé sur la réflexion, etc.). J'ai donc décidé de la classer, ce qui n'est pas anodin à première vue, parce que la méthode pour dire s'il faut inclure une propriété (isGetMethod) est privée.

mais, on pourrait outrepasser la méthode de normalisation, donc j'ai intercepté à ce point, en désactivant simplement la propriété qui fait référence" Location " - de sorte que la boucle inifinite est interrompue.

en code il 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
répondu oxy 2011-07-27 17:41:25

j'ai eu le même problème et j'ai choisi de créer mon propre encodeur, qui va faire face par lui-même avec la récursion.

j'ai créé des classes qui mettent en œuvre Symfony\Component\Serializer\Normalizer\NormalizerInterface , et un service qui tient 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);
}

exemple d'un 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
répondu Julien Fastré 2013-06-16 17:11:12

il s'agit plus d'une mise à jour (pour Symfony v:2.7+ et JmsSerializer v:0.13.* @dev) , donc pour éviter que Jms tente de charger et de sérialiser le graphe objet entier ( ou en cas de relation cyclique ..)

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
répondu mboullouz 2016-03-06 21:15:33

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.

5
répondu Lebnik 2013-11-28 15:04:11

si vous utilisez Symfony 2.7 ou plus , et que vous ne voulez pas inclure de paquets supplémentaires pour sérialiser, peut-être Pouvez-vous suivre cette voie pour seialiser les entités de doctrine à 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 l'utiliser pour sérialiser des Entités en JSON

    $this->_getSerializer()->normalize($anEntity, 'json');
    $this->_getSerializer()->normalize($arrayOfEntities, 'json');
    

fait!

mais vous pourriez avoir besoin de quelques réglages. Par exemple -

4
répondu Anis 2016-01-06 09:07:04

lorsque vous avez besoin de créer un grand nombre de paramètres de L'API REST sur Symfony, la meilleure façon est d'utiliser la pile de paquets suivante:

  1. JMSSerializerBundle pour la sérialisation des entités Doctrine
  2. FOSRestBundle bundle pour la réponse de vue de l'auditeur. Il peut aussi générer une définition des routes basée sur le nom du contrôleur/de l'action.
  3. NelmioApiDocBundle pour auto-générer de la documentation en ligne et bac à sable(ce qui permet de tester endpoint 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;
}

puis, 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 de cette installation sont:

  • @JMS\Expose() les annotations dans entity peuvent être ajoutées à des champs simples, et à n'importe quel les types de relations. Il est également possible d'exposer le résultat de l'exécution d'une méthode (utilisez 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. Méthode d'Action peut retourner directement à une entité ou groupe d'entités, et ils seront automatiquement activé.
  • et @ApiDoc() permet de tester le paramètre directement à partir du navigateur, sans REST client ou code JavaScript
3
répondu Maksym Moskvychev 2016-09-13 15:32:05

Maintenant, vous pouvez également utiliser Doctrine ORM Transformations pour convertir des entités en réseaux imbriqués de scalaires et retour

2
répondu ScorpioT1000 2018-05-17 08:25:05