【问题标题】:How to encode Doctrine entities to JSON in Symfony 2.0 AJAX application?如何在 Symfony 2.0 AJAX 应用程序中将 Doctrine 实体编码为 JSON?
【发布时间】: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 来避免循环......,

【问题讨论】:

    标签: ajax doctrine doctrine-orm symfony


    【解决方案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 行
    【解决方案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 中的数组

      【讨论】:

      【解决方案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 -

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

            完成!

            但您可能需要进行一些微调。例如 -

            【讨论】:

              【解决方案11】:

              当您需要在 Symfony 上创建大量 REST API 端点时, 最好的方法是使用以下捆绑包:

              1. JMSSerializerBundle 用于 Doctrine 实体的序列化
              2. FOSRestBundle 用于响应视图侦听器的捆绑包。此外,它还可以根据控制器/动作名称生成路由定义。
              3. 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 上测试过

                  【讨论】:

                    猜你喜欢
                    • 2016-06-18
                    • 1970-01-01
                    • 2020-03-02
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2019-04-29
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多