symfony3 - ¿Cómo codificar las entidades de Doctrine en JSON en la aplicación AJAX de Symfony 2.0?
symfony return json (12)
Estoy desarrollando aplicaciones de juegos y estoy usando Symfony 2.0. Tengo muchas solicitudes AJAX al back-end. Y más respuestas es convertir la entidad a JSON. Por ejemplo:
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'');
}
}
Y todos mis controladores hacen lo mismo: obtener una entidad y codificar algunos de sus campos en JSON. Sé que puedo usar normalizadores y codificar todas las entidades. Pero, ¿qué ocurre si una entidad ha ciclado enlaces a otra entidad? ¿O el gráfico de entidades es muy grande? ¿Tienes alguna sugerencia?
Pienso en algún esquema de codificación para entidades ... o usando NormalizableInterface
para evitar el ciclismo ..,
en 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 }
y ejemplo para su controlador:
/**
* Поиск сущности по ИД объекта и ИД языка
* @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 );
}
pero los problemas con el tipo de campo / DateTime se mantendrán.
Ahora también puede usar Doctrine ORM Transformations para convertir entidades a matrices anidadas de escalares y viceversa
Con php5.4 ahora puedes hacer:
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,
);
}
}
Y luego llama
json_encode(MyUserEntity);
Cuando necesite crear una gran cantidad de puntos finales REST API en Symfony, la mejor manera es usar la siguiente pila de paquetes:
- JMSSerializerBundle para la serialización de entidades de Doctrine
- FOSRestBundle para escucha de vista de respuesta. También puede generar una definición de rutas basada en el nombre del controlador / acción.
- NelmioApiDocBundle para autogenerar documentación en línea y Sandbox (que permite probar el punto final sin ninguna herramienta externa).
Cuando configuras todo correctamente, tu código de entidad se verá así:
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;
}
Luego, codifica en el controlador:
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();
}
}
Los beneficios de dicha configuración son:
- Las anotaciones @JMS / Expose () en entidad se pueden agregar a campos simples y a cualquier tipo de relación. También existe la posibilidad de exponer el resultado de la ejecución de algún método (use la anotación @JMS / VirtualProperty () para eso)
- Con los grupos de serialización podemos controlar los campos expuestos en diferentes situaciones.
- Los controladores son muy simples. El método de acción puede devolver directamente una entidad o matriz de entidades, y se serializarán automáticamente.
- Y @ApiDoc () permite probar el punto final directamente desde el navegador, sin ningún cliente REST o código JavaScript
Encontré que la solución al problema de la serialización de entidades era la siguiente:
#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 }
en mi controlador:
$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));
otro ejemplo:
$serializer = $this->get(''serializer'');
$collection = $this->get(''doctrine'')
->getRepository(''myBundle:Entity'')
->findBy($params);
$json = $serializer->serialize($collection, ''json'');
return new Response($json);
incluso puedes configurarlo para deserializar matrices en http://api.symfony.com/2.0
Esto es más una actualización (para Symfony v: 2.7+ y JmsSerializer v: 0.13. * @ Dev) , para evitar que Jms intente cargar y serializar todo el gráfico del objeto (o en el caso de una relación cíclica ...)
Modelo:
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
......................
......................
*/
Dentro de una acción:
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);
Otra opción es usar el JMSSerializerBundle . En tu controlador, entonces haces
$serializer = $this->container->get(''serializer'');
$reports = $serializer->serialize($doctrineobject, ''json'');
return new Response($reports); // should be $reports as $doctrineobject is not serialized
Puede configurar cómo se realiza la serialización mediante el uso de anotaciones en la clase de entidad. Vea la documentación en el enlace de arriba. Por ejemplo, así es como excluirías las entidades vinculadas:
/**
* 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;
Para completar la respuesta: Symfony2 viene con un contenedor alrededor de json_encode: Symfony/Component/HttpFoundation/JsonResponse
Uso típico en sus Controladores:
...
use Symfony/Component/HttpFoundation/JsonResponse;
...
public function acmeAction() {
...
return new JsonResponse($array);
}
Espero que esto ayude
J
Puedes codificar automáticamente en Json, tu entidad compleja con:
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'');
Si está utilizando Symfony 2.7 o superior , y no desea incluir ningún paquete adicional para la serialización, tal vez pueda seguir de esta manera para seializar entidades de doctrina a json -
En mi controlador (común, principal), tengo una función que prepara el serializador
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()]); }
Luego úsela para serializar Entidades a JSON
$this->_getSerializer()->normalize($anEntity, ''json''); $this->_getSerializer()->normalize($arrayOfEntities, ''json'');
¡Hecho!
Pero es posible que necesite un ajuste fino. Por ejemplo -
- Si sus entidades tienen una referencia circular, compruebe cómo manejarla .
- Si quiere ignorar algunas propiedades, puede hacerlo
- Aún mejor, puedes serializar solo atributos selectivos .
Solo tuve que resolver el mismo problema: json-codificación de una entidad ("Usuario") que tiene una Asociación Bidireccional Uno a Muchos a otra Entidad ("Ubicación").
Intenté varias cosas y creo que ahora encontré la mejor solución aceptable. La idea era usar el mismo código escrito por David, pero de alguna manera interceptar la recursión infinita al decirle al Normalizador que se detuviera en algún momento.
No quería implementar un normalizador personalizado, ya que este GetSetMethodNormalizer es un buen enfoque en mi opinión (basado en la reflexión, etc.). Así que decidí subclasificarlo, lo cual no es trivial a primera vista, porque el método para decir si incluir una propiedad (isGetMethod) es privado.
Pero, uno podría anular el método de normalización, así que lo intercepté en este punto, simplemente desarmando la propiedad que hace referencia a "Ubicación", por lo que se interrumpe el ciclo de infinito.
En el código se ve así:
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);
}
}
Tuve el mismo problema y elegí crear mi propio codificador, que se las arreglará solo con la recursión.
Creé clases que implementa Symfony/Component/Serializer/Normalizer/NormalizerInterface
, y un servicio que contiene cada 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 ejemplo de un Normalizador:
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.)
);
}
}
En un controlador:
$normalizerService = $this->get(''normalizer.service''); //you will have to configure services.yml
$json = $normalizerService->serialize($myobject, ''json'');
return new Response($json);
El código completo está aquí: https://github.com/progracqteur/WikiPedale/tree/master/src/Progracqteur/WikipedaleBundle/Resources/Normalizer