Как кодировать сущности доктрины в JSON в приложении Symfony 2.0 AJAX?
Я разрабатываю игровое приложение и использую Symfony 2.0. У меня много запросов AJAX к бэкэнду. И больше ответов преобразует сущность в JSON. Например:
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');
}
}
и все мои контроллеры делают то же самое: получают сущность и кодируют некоторые из ее полей в JSON. Я знаю, что могу использовать нормализаторы и кодировать все права. Но что, если сущность имеет циклические связи с другой сущностью? Или график сущностей очень большой? У вас есть предложения?
Я думаю о некоторая схема кодирования для сущностей... или используя NormalizableInterface
чтобы избежать езды на велосипеде..,
12 ответов
другой вариант-использовать JMSSerializerBundle. В вашем контроллере вы тогда делаете
$serializer = $this->container->get('serializer');
$reports = $serializer->serialize($doctrineobject, 'json');
return new Response($reports); // should be $reports as $doctrineobject is not serialized
можно настроить способ сериализации с помощью аннотаций в классе сущностей. См. документацию по ссылке выше. Например, вот как можно исключить связанные сущности:
/**
* 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;
С php5.4 Теперь вы можете сделать :
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,
);
}
}
а затем вызовите
json_encode(MyUserEntity);
вы можете автоматически кодировать в Json, ваш сложный объект с:
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');
для полноты ответа: Symfony2 включает оболочку json_encode: Symfony / Component/HttpFoundation / JsonResponse
типичное использование в контроллерах:
...
use Symfony\Component\HttpFoundation\JsonResponse;
...
public function acmeAction() {
...
return new JsonResponse($array);
}
надеюсь, что это помогает
J
Я нашел решение проблемы сериализации сущностей выглядит следующим образом:
#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 }
в мой контроллер:
$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));
другой пример:
$serializer = $this->get('serializer');
$collection = $this->get('doctrine')
->getRepository('myBundle:Entity')
->findBy($params);
$json = $serializer->serialize($collection, 'json');
return new Response($json);
вы даже можете настроить его для десериализации массивов в http://api.symfony.com/2.0
Мне просто нужно было решить ту же проблему: JSON-кодирование сущности ("пользователь"), имеющей двунаправленную связь"один ко многим"с другой сущностью ("местоположение").
я попробовал несколько вещей, и я думаю, что теперь я нашел лучшее приемлемое решение. Идея заключалась в том, чтобы использовать тот же код, что и Дэвид, но каким-то образом перехватить бесконечную рекурсию, сказав Нормализатору остановиться в какой-то момент.
Я не хотел реализовывать пользовательский нормализатор, так как это GetSetMethodNormalizer-хороший подход, на мой взгляд (основанный на размышлении и т. д.). Поэтому я решил подклассировать его, что на первый взгляд не тривиально, потому что метод, чтобы сказать, включать ли свойство (isGetMethod), является частным.
но можно переопределить метод normalize, поэтому я перехватил в этот момент, просто сбросив свойство, которое ссылается на "Location" - поэтому цикл inifinite прерывается.
в коде это выглядит так:
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);
}
}
у меня была та же проблема, и я решил создать свой собственный кодер, который справится сам с рекурсией.
Я создал классы, которые реализует Symfony\Component\Serializer\Normalizer\NormalizerInterface
, и служба, которая держит каждый 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);
}
пример нормализатора:
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.)
);
}
}
в контроллере :
$normalizerService = $this->get('normalizer.service'); //you will have to configure services.yml
$json = $normalizerService->serialize($myobject, 'json');
return new Response($json);
полный код здесь : https://github.com/progracqteur/WikiPedale/tree/master/src/Progracqteur/WikipedaleBundle/Resources/Normalizer
Это больше обновлений (для Symfony v:2.7+ и JmsSerializer v:0.13 - ... * @dev), чтобы избежать того, что Jms пытается загрузить и сериализовать весь граф объекта ( или в случае циклического отношения ..)
модель:
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
......................
......................
*/
внутри действий:
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);
в 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 }
и пример для вашего контроллера:
/**
* Поиск сущности по ИД объекта и ИД языка
* @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 );
}
но проблемы с типом поля \DateTime останутся.
Если вы используете в Symfony 2.7 или выше, и не хотите включать какой - либо дополнительный пакет для сериализации, возможно, вы можете следовать этому пути для seialize сущностей доктрины в json -
-
в моем (общем, родительском) контроллере у меня есть функция, которая готовит сериализатор
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()]); }
-
затем используйте его для сериализации сущностей в JSON
$this->_getSerializer()->normalize($anEntity, 'json'); $this->_getSerializer()->normalize($arrayOfEntities, 'json');
готово!
но вам может понадобиться тонкая настройка. Например -
- если ваши сущности имеют круговую ссылку, проверьте, как с ним обращаться.
- если вы хотите игнорировать некоторые свойства, можете сделать это
- еще лучше, вы можете сериализовать только выборочные атрибуты.
когда вам нужно создать много конечных точек REST API на Symfony, лучший способ-использовать следующий стек пакетов:
- JMSSerializerBundle для сериализации сущностей доктрины
- FOSRestBundle bundle для прослушивателя представления ответа. Также он может генерировать определение маршрутов на основе имени контроллера/действия.
- NelmioApiDocBundle для автоматического создания онлайн-документации и песочницы(которая позволяет тестировать конечную точку без какого-либо внешнего инструмента).
когда вы настроите все правильно, код сущности будет выглядеть так:
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;
}
затем, код в контроллере:
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();
}
}
преимущества такой установки:
- @JMS\Expose() аннотации в сущности могут быть добавлены к простым полям и к любым типам отношений. Также есть возможность выставить результат выполнения некоторого метода (используйте аннотацию @JMS\VirtualProperty () для что)
- С помощью групп сериализации мы можем управлять открытыми полями в разных ситуациях.
- контроллеры очень просты. Метод действия могут напрямую возвращать объект или массив объектов, и они будут автоматически сериализован.
- и @ApiDoc () позволяет тестировать конечную точку непосредственно из браузера, без какого-либо клиента REST или кода JavaScript
теперь вы также можете использовать доктрина ORM преобразования для преобразования сущностей во вложенные массивы скаляров и обратно