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

如何在Symfony 2.0 AJAX应用程序中将条令实体编码为JSON?,ajax,doctrine,doctrine-orm,symfony,Ajax,Doctrine,Doctrine Orm,Symfony,我正在开发游戏应用程序并使用Symfony 2.0。我对后端有很多AJAX请求。更多的响应是将实体转换为JSON。例如: class DefaultController extends Controller { public function launchAction() { $user = $this->getDoctrine() ->getRepository('UserBund

我正在开发游戏应用程序并使用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
避免循环..,

您可以自动将复杂实体编码为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');

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

我尝试了几件事,我想现在我找到了最好的可接受的解决方案。其思想是使用与David编写的代码相同的代码,但通过告诉规范化程序在某个点停止,以某种方式拦截无限递归

我不想实现自定义规范化器,因为在我看来,这个GetSetMethodNormalizer是一个不错的方法(基于反射等)。所以我决定对它进行子类化,这乍一看并不简单,因为表示是否包含属性的方法(isGetMethod)是私有的

但是,可以重写normalize方法,所以我在这里截取了它,只需取消设置引用“Location”的属性,就可以中断有限循环

在代码中,它如下所示:

类GetSetMethodNormalizer扩展\Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer{
公共函数规格化($object,$format=null)
{
//如果对象是用户,则在不接触原始对象的情况下取消设置规范化的位置
if($object instanceof\Leonex\MoveBundle\Entity\User){
$object=克隆$object;
$object->setLocations(new\Doctrine\Common\Collections\ArrayCollection());
}
返回父项::normalize($object,$format);
}
} 

另一个选项是使用。然后在控制器中执行以下操作:

$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;

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

#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));
/**
 * Поиск сущности по ИД объекта и ИД языка
 * @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 );
}
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();
    }
}
其他例子:

$serializer = $this->get('serializer');

$collection = $this->get('doctrine')
               ->getRepository('myBundle:Entity')
               ->findBy($params);

$json = $serializer->serialize($collection, 'json');

return new Response($json);

您甚至可以使用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);

我也有同样的问题,我选择创建自己的编码器,它将自己处理递归

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

完整的代码如下:

要完成答案:Symfony2附带了一个围绕json_encode的包装器:

控制器中的典型用法:

...
use Symfony\Component\HttpFoundation\JsonResponse;
...
public function acmeAction() {
...
return new JsonResponse($array);
}
在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 }
和控制器的示例:

$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));
/**
 * Поиск сущности по ИД объекта и ИД языка
 * @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 );
}
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();
    }
}

但是字段类型\DateTime的问题仍然存在。

这更像是一个更新(对于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
      ......................
      ......................
   */

如果您使用的是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');
    
  • 完成了

    但您可能需要一些微调。例如—

    • 如果实体具有循环引用
    • 如果要忽略某些属性
    • 更好的是,您可以序列化

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

  • 用于实体的序列化
  • 响应视图侦听器的捆绑包。它还可以根据控制器/操作名称生成路由定义
  • 自动生成在线文档和沙箱(无需任何外部工具即可测试端点)
  • 正确配置所有内容后,实体代码将如下所示:

    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;
    }
    
    然后,在控制器中编码:

    $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));
    
    /**
     * Поиск сущности по ИД объекта и ИД языка
     * @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 );
    }
    
    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()注释可以添加到简单字段和任何类型的关系中。还有可能公开某些方法执行的结果(为此使用annotation@JMS\VirtualProperty()
    • 通过序列化组,我们可以在不同的情况下控制公开的字段
    • 控制器非常简单。Action方法可以直接返回一个实体或实体数组,它们将被自动序列化
    • @ApiDoc()允许直接从浏览器测试端点,无需任何REST客户端或JavaScript代码

    现在,您还可以使用将实体转换为嵌套的标量数组并返回

    接受的答案是正确的,但如果您需要序列化实体的过滤子集,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上测试过,谢谢,但我有一个玩家实体,它有到游戏实体集合的链接,每个游戏实体都有到在其中玩过的玩家的链接。像这样的。你认为GetSetMethodNormalizer会正确工作吗(它使用递归算法)?是的,它是递归的,这是我的问题。因此,对于特定的实体,您可以使用CustomNormalizer及其Normalizable接口