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


89

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

Answers:


82

另一个选择是使用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
您需要添加 使用JMS \ SerializerBundle \ Annotation \ ExclusionPolicy;使用JMS \ SerializerBundle \ Annotation \ Exclude; 在您的实体中并安装JMSSerializerBundle以便使其正常工作
ioleo 2012年

3
如果将其更改为,效果很好:return new Response($ reports);
Greywire

7
由于注释已从捆绑包中移出,因此正确的use语句现在为:use JMS \ Serializer \ Annotation \ ExclusionPolicy; 使用JMS \ Serializer \ Annotation \ Exclude;
2013年

3
Doctrine的文档说不要序列化对象或非常小心地进行序列化。
Bluebaron

我什至不需要安装JMSSerializerBundle。您的代码无需JMSSerializerBundle即可工作。
Derk Jan Speelman,

147

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

1
我非常喜欢这个解决方案!
迈克尔

3
如果您想将对其他捆绑软件的依赖性降至最低,这是一个很好的解决方案……
Drmjo 2015年

5
链接实体呢?
开膛手约翰(John the Ripper)

7
这似乎不适用于实体集合(即OneToMany关系)
Pierre de LESPINAY 2016年

1
这违反了单一责任原则,并且如果您的实体是由教义自动生成的,那就不好了
Jim Smith

39

您可以使用以下方法自动将其编码为复杂的实体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实体,它具有指向Game实体集合的链接,每个Game实体都具有指向其中玩过的玩家的链接。这样的事情。而且您认为GetSetMethodNormalizer是否可以正常工作(它使用递归算法)?
Dmytro Krasun 2011年

2
是的,它是递归的,这就是我的问题。因此,对于特定的实体,您可以使用CustomNormalizer及其规范化接口,如您所知。
webda2l 2011年

2
当我尝试此操作时,出现“致命错误:允许在/home/jason/pressbox/vendor/symfony/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php中耗尽134217728字节的内存大小(尝试分配64字节)。第44行”。我想知道为什么?
杰森·斯威特

1
当我尝试时,出现以下异常。.致命错误:达到“ 100”的最大函数嵌套级别,正在中止!在C:\ wamp \ www \ myapp \ application \ libraries \ doctrine \ Symfony \ Component \ Serializer \ Normalizer \ GetSetMethodNormalizer.php在第223行
user2350626



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+中使用Serializer组件的食谱条目,因为您现在可以激活内置的组件:symfony.com/doc/current/cookbook/serializer.html
althaus 2014年

6

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

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

我不想实现自定义规范化器,因为我认为此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.从来不需要触摸Entity类,2.不仅将“ Locations”空白,而且将每个Collections类型字段都可能映射到其他Entites。即无需序列化它的内部/高级知识,无递归。
Marcos

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

规范化器的示例:

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

这是一个更新(对于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);

5

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

做完了!

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


4

当您需要在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代码

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.