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

95

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

13个回答

156

现在,在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);

4
如果你想尽量减少对其他捆绑包的依赖,这是一个很好的解决方案。 - Drmjo
7
关于关联实体呢? - John the Ripper
8
这似乎不适用于实体集合(即“OneToMany”关系)。 - Pierre de LESPINAY
3
这违反了单一职责原则,如果您的实体是由Doctrine自动生成的,则不好。 - jim smith

85

另一种选择是使用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;

7
你需要在实体中添加以下代码:*use JMS\SerializerBundle\Annotation\ExclusionPolicy; use JMS\SerializerBundle\Annotation\Exclude;*,并安装JMSSerializerBundle才能使其正常工作。 - ioleo
3
如果您将其更改为“return new Response($reports)”,它将运行得很好。 - Greywire
7
由于注释已经移出捆绑包,正确的使用语句现在是:use JMS\Serializer\Annotation\ExclusionPolicy; use JMS\Serializer\Annotation\Exclude; - Pier-Luc Gendreau
3
Doctrine 的文档建议不要将对象序列化或者在序列化时要非常小心。 - Bluebaron
我甚至不需要安装JMSSerializerBundle。你的代码可以在不需要JMSSerializerBundle的情况下工作。 - Derk Jan Speelman

40

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

3
谢谢,但我有一个名为“Player”的实体,它与一个游戏实体集合相关联,每个游戏实体都链接到在其中参与的玩家。就像这样。你认为GetSetMethodNormalizer会正确工作吗(它使用递归算法)? - Dmytro Krasun
2
是的,它是递归的,这在我的情况下是我的问题。 因此,对于特定实体,您可以使用CustomNormalizer及其NormalizableInterface,正如您所知道的那样。 - webda2l
2
当我尝试这个时,我得到了“致命错误:在/home/jason/pressbox/vendor/symfony/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php的第44行尝试分配64字节时,已耗尽134217728字节的允许内存大小”。我想知道为什么? - Jason Swett
1
当我尝试时,出现了以下异常:致命错误:已达到“100”的最大函数嵌套级别,正在中止!位于C:\ wamp \ www \ myapp \ application \ libraries \ doctrine \ Symfony \ Component \ Serializer \ Normalizer \ GetSetMethodNormalizer.php的第223行。 - user2350626
1
@user2350626,请查看https://dev59.com/92855IYBdhLWcg3whEtL。 - webda2l
1
我尝试过那个,但是它没有起作用。目前为了解决这个问题,我采用了SparSio建议的方法。 - user2350626

12
完成回答:Symfony2附带了一个json_encode的包装器: Symfony/Component/HttpFoundation/JsonResponse 在您的控制器中的典型用法:
...
use Symfony\Component\HttpFoundation\JsonResponse;
...
public function acmeAction() {
...
return new JsonResponse($array);
}

10

我找到了解决序列化实体问题的方法:

#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中的数组


3
关于在Symfony 2.3+中使用序列化器组件的食谱条目,现在可以激活内置的序列化器:http://symfony.com/doc/current/cookbook/serializer.html - althaus

6

我曾经遇到过相同的问题,于是选择创建自己的编码器,可以自行处理递归。

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

6
这是关于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
      ......................
      ......................
   */

在Action内部:

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

6

in 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 字段类型相关的问题仍将存在。


6
我刚刚必须解决同样的问题:对一个实体(“User”)进行json编码,该实体与另一个实体(“Location”)具有一对多的双向关联。我尝试了几种方法,现在我认为我找到了最好的可接受的解决方案。想法是使用与David编写的相同代码,但以某种方式拦截无限递归,并告诉规范化器在某个点停止。
我不想实现自定义规范化器,因为我认为GetSetMethodNormalizer是一种很好的方法(基于反射等)。所以我决定对其进行子类化,这并不是一件简单的事情,因为表示是否包括属性(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);
    }

} 

1
我想知道是否可以很容易地将其概括,以便 1. 永远不需要触摸实体类,2. 不仅仅是清空“位置”,而是每个可能映射到其他实体的集合类型字段。即在序列化它时不需要内部/高级Ent知识,也不会出现递归。 - Marcos

5

如果您使用的是Symfony 2.7或更高版本,并且不想包含任何额外的bundle来进行序列化,则可以按照以下方式将doctrine实体序列化为json -

  1. In my (common, parent) controller, I have a function that prepares the serializer

    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. Then use it to serialize Entities to JSON

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

完成了!

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接