Как кодировать объекты доктрины в 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, поэтому я перехватил в этот момент, просто сбросив свойство, которое ссылается на" местоположение", - поэтому цикл 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
/приложение/конфигурация/конфиг.в формате 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 Doctrine entities to 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 пакет для прослушивателя представления ответа. Также он может генерировать определение маршрутов на основе имени контроллера/действия.
- 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 преобразования для преобразования сущностей во вложенные массивы скаляров и обратно