如何在 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?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Symfony 2.0 中使用 Linux Xampp mysql_pdo

如何在 symfony2 中提交表单 ajax?

symfony2 上的 AJAX 跨域

Symfony 2.0 在实体内部获取服务

如何使用Symfony从ajax请求中正确返回html模板

如何使用 Symfony 和 Jquery 发出 POST Ajax 请求