如何在Symfony 2.0 AJAX应用程序中将Doctrine实体编码为JSON?

我正在开发游戏应用程序,并使用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。 我知道我可以使用规范化器并编码所有的实体。 但是,如果一个实体已经循环到其他实体的链接呢? 或者实体图很大? 你有什么build议吗?

我想一些实体编码模式…或使用NormalizableInterface避免骑自行车..,

另一种select是使用JMSSerializerBundle 。 在你的控制器,然后你做

 $serializer = $this->container->get('serializer'); $reports = $serializer->serialize($doctrineobject, 'json'); return new Response($reports); // should be $reports as $doctrineobject is not serialized 

您可以configuration如何通过在实体类中使用注释来完成序列化。 请参阅上面链接中的文档。 例如,下面是如何排除链接的实体:

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

希望这可以帮助

Ĵ

我发现解决序列化实体的问题如下:

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

您甚至可以将其configuration为http://api.symfony.com/2.0中的反序列化数组;

我只需要解决同样的问题:json编码一个实体(“用户”)具有一对多双向关联到另一个实体(“位置”)。

我尝试了几件事情,现在我认为我find了最好的可接受的解决scheme。 这个想法是使用David编写的相同的代码,但是通过告诉Normalizer在某个点停止,以某种方式拦截无限recursion。

我不想实现一个自定义的规范化,因为这个GetSetMethodNormalizer在我看来是一个不错的方法(基于reflection等)。 所以我已经决定子类化它,一见钟情并不重要,因为说包含属性(isGetMethod)的方法是私有的。

但是,我们可以重写normalize方法,所以我在这一点截取,只需简单地取消引用“Location”的属性 – 所以无限循环被中断。

在代码中,它看起来像这样:

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

我有同样的问题,我select了创build自己的编码器,这将与recursion应付自己。

我创build了实现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); } 

一个Normalizer的例子:

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

但字段types\ DateTime的问题将保留。

如果您使用的是Symfony 2.7或更高版本 ,并且不想包含用于序列化的任何附加包,也许您可​​以按照这种方式将doctrine实体seialize为json –

  1. 在我的(通用,父母)控制器中,我有一个准备串行器的函数

     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. 然后用它来将实体序列化为JSON

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

完成!

但是你可能需要一些微调。 例如 –

  • 如果您的实体有循环引用, 请检查如何处理它 。
  • 如果你想忽略一些属性, 可以做到这一点
  • 更好的是,你可以序列化只有select性的属性 。

当您需要在Symfony上创build大量的REST API端点时,最好的方法是使用以下的bundle集合:

  1. JMSSerializerBundle用于Doctrine实体的序列化
  2. 响应视图侦听器的FOSRestBundle包。 也可以根据控制器/操作名称生成路由的定义。
  3. NelmioApiDocBundle自动生成在线文档和沙盒(允许在没有任何外部工具的情况下testing端点)。

当你正确地configuration一切,你的实体代码将如下所示:

 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()注释可以添加到简单字段以及任何types的关系中。 也有可能公开一些方法执行的结果(使用注解@JMS \ VirtualProperty())
  • 使用序列化组,我们可以在不同情况下控制暴露的字段。
  • 控制器非常简单。 Action方法可以直接返回实体或实体数组,并自动序列化。
  • 而@ApiDoc()允许直接从浏览器testing端点,而不需要任何REST客户端或JavaScript代码

现在,您也可以使用Doctrine ORM Transformations将实体转换为标量数组并返回