如何在 Symfony 2.0 AJAX 应用程序中将 Doctrine 实体编码为 JSON?
Posted
技术标签:
【中文标题】如何在 Symfony 2.0 AJAX 应用程序中将 Doctrine 实体编码为 JSON?【英文标题】:How to encode Doctrine entities to JSON in Symfony 2.0 AJAX application? 【发布时间】:2011-10-06 02:18:01 【问题描述】:我正在开发游戏应用程序并使用 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
来避免循环......,
【问题讨论】:
【参考方案1】:现在你可以用 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);
【讨论】:
如果您想尽量减少对其他捆绑包的依赖,这是一个很好的解决方案... 链接实体呢? 这似乎不适用于实体集合(即:OneToMany
关系)
这违反了单一责任原则,如果你的实体是由学说自动生成的,那就不好了【参考方案2】:
另一种选择是使用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;
【讨论】:
你需要添加 use JMS\SerializerBundle\Annotation\ExclusionPolicy;在您的实体中使用 JMS\SerializerBundle\Annotation\Exclude; 并安装 JMSSerializerBundle 以使其正常工作 如果将其更改为效果很好:return new Response($reports); 由于注解已经从包中移出,正确的使用语句现在是:use JMS\Serializer\Annotation\ExclusionPolicy;使用 JMS\Serializer\Annotation\Exclude; Doctrine 的文档说不要序列化对象或非常小心地序列化。 我什至不需要安装 JMSSerializerBundle。您的代码无需 JMSSerializerBundle 即可工作。【参考方案3】:您可以通过以下方式自动编码为 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');
【讨论】:
谢谢,但我有玩家实体链接到游戏实体集合,每个游戏实体都链接到在其中玩过的玩家。像这样的东西。你认为 GetSetMethodNormalizer 会正确工作吗(它使用递归算法)? 是的,它是递归的,这就是我的问题。因此,对于特定实体,您可以使用您似乎知道的 CustomNormalizer 及其 NormalizableInterface。 当我尝试这个时,我得到“致命错误:在 /home/jason/pressbox/vendor/symfony/src/Symfony/Component/ 中允许的内存大小为 134217728 字节已用尽(试图分配 64 字节)第 44 行的 Serializer/Normalizer/GetSetMethodNormalizer.php"。我想知道为什么? 当我尝试时,我得到了以下异常.. 致命错误:达到了“100”的最大函数嵌套级别,正在中止!在 C:\wamp\www\myapp\application\libraries\doctrine\Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer.php 第 223 行 @user2350626,见***.com/questions/4293775/…【参考方案4】:为了完成答案:Symfony2 带有一个 json_encode 的包装器: Symfony/Component/HttpFoundation/JsonResponse
控制器中的典型用法:
...
use Symfony\Component\HttpFoundation\JsonResponse;
...
public function acmeAction()
...
return new JsonResponse($array);
【讨论】:
【参考方案5】:我发现实体序列化问题的解决方法如下:
#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 中的数组
【讨论】:
有一个关于在 Symfony 2.3+ 中使用 Serializer 组件的食谱条目,因为您现在可以激活内置组件:symfony.com/doc/current/cookbook/serializer.html【参考方案6】:我只需要解决同样的问题:对一个实体(“用户”)进行 json 编码,该实体(“用户”)与另一个实体(“位置”)具有一对多双向关联。
我尝试了几件事,我认为现在我找到了可接受的最佳解决方案。想法是使用与 David 编写的代码相同的代码,但以某种方式通过告诉 Normalizer 在某个点停止来拦截无限递归。
我不想实现自定义规范化器,因为在我看来这个 GetSetMethodNormalizer 是一个不错的方法(基于反射等)。所以我决定对它进行子类化,这乍一看并不简单,因为判断是否包含属性的方法(isGetMethod)是私有的。
但是,可以覆盖 normalize 方法,所以我在这一点上截获了,只需取消设置引用“位置”的属性 - 因此无限循环被中断。
在代码中是这样的:
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);
【讨论】:
我想知道概括这一点有多容易,因此 1. 永远不需要接触实体类,2. 不仅空白“位置”,而且每个集合类型字段都可能映射到其他实体。 IE。不需要 Ent 的内部/高级知识来序列化它,无递归。【参考方案7】:我遇到了同样的问题,我选择创建自己的编码器,它可以自己处理递归。
我创建了实现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
【讨论】:
【参考方案8】:在 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 的问题仍然存在。
【讨论】:
【参考方案9】:这更像是一个更新(对于 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);
【讨论】:
【参考方案10】:如果您使用的是 Symfony 2.7 或更高版本,并且不想包含任何额外的捆绑包进行序列化,也许您可以按照这种方式将学说实体序列化为 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');
完成!
但您可能需要进行一些微调。例如 -
如果您的实体有循环引用,check how to handle it。 如果你想忽略某些属性,can do it 更好的是,你可以序列化only selective attributes。【讨论】:
【参考方案11】:当您需要在 Symfony 上创建大量 REST API 端点时, 最好的方法是使用以下捆绑包:
-
JMSSerializerBundle 用于 Doctrine 实体的序列化
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()
)
通过序列化组,我们可以控制不同情况下的暴露字段。
控制器非常简单。 action 方法可以直接返回一个实体或实体数组,它们会被自动序列化。
@ApiDoc()
允许直接从浏览器测试端点,无需任何 REST 客户端或 javascript 代码
【讨论】:
【参考方案12】:现在您还可以使用Doctrine ORM Transformations 将实体转换为嵌套的标量数组并返回
【讨论】:
【参考方案13】:接受的答案是正确的,但如果您需要序列化 Entity 的过滤子集,json_encode 就足够了:
考虑这个例子:
class FileTypeRepository extends ServiceEntityRepository
const ALIAS = 'ft';
const SHORT_LIST = 'ft.name name';
public function __construct(ManagerRegistry $registry)
parent::__construct($registry, FileType::class);
public function getAllJsonFileTypes()
return json_encode($this->getAllFileTypes());
/**
* @return array
*/
public function getAllFileTypes()
$query = $this->createQueryBuilder(self::ALIAS);
$query->select(self::SHORT_LIST);
return $query->getQuery()->getResult();
/** THIS IS ENOUGH TO SERIALIZE AN ARRAY OF ENTITIES SINCE the doctrine SELECT will remove complex data structures from the entities itself **/
json_encode($this->getAllFileTypes());
简短说明:至少在 Symfony 5.1 上测试过
【讨论】:
以上是关于如何在 Symfony 2.0 AJAX 应用程序中将 Doctrine 实体编码为 JSON?的主要内容,如果未能解决你的问题,请参考以下文章