Symfony 2.0 AJAX 애플리케이션에서 Doctrine 엔티티를 JSON으로 인코딩하는 방법은 무엇입니까?


91

게임 앱을 개발 중이며 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순환을 피하기 위해 사용 합니다 ..,

답변:


83

또 다른 옵션은 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 를 추가해야합니다 . JMS \ SerializerBundle \ Annotation \ Exclude를 사용하십시오. 당신의 개체에 작업이 순서대로 JMSSerializerBundle를 설치
ioleo

3
다음과 같이 변경하면 잘 작동합니다. return new Response ($ reports);
Greywire 2013

7
어노테이션이 번들에서 이동되었으므로 이제 올바른 use 문은 다음과 같습니다. use JMS \ Serializer \ Annotation \ ExclusionPolicy; JMS \ Serializer \ Annotation \ Exclude를 사용하십시오.
Pier-Luc

3
Doctrine에 대한 문서에는 객체를 직렬화하거나 세 심하게 직렬화하지 말라고되어 있습니다.
Bluebaron

JMSSerializerBundle을 설치할 필요조차 없었습니다. 코드는 JMSSerializerBundle 없이도 작동했습니다.
Derk Jan Speelman

149

이제 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
나는이 솔루션을 매우 좋아합니다!
Michael

3
다른 번들에 대한 종속성을 최소한으로 유지하려는 경우 훌륭한 솔루션입니다.
Drmjo

5
연결된 엔티티는 어떻습니까?
John the Ripper

7
(즉,이 엔티티의 컬렉션으로 작동하지 않는 OneToMany관계)
피에르 드 LESPINAY

1
이는 단일 책임 원칙을 위반하고 개체가 자동 교리에 의해 발생하는 경우 좋지 않습니다
짐 스미스

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
감사합니다.하지만 Game 엔티티 컬렉션에 대한 링크가있는 Player 엔티티가 있고 모든 Game 엔티티에는 그 안에서 플레이 한 플레이어에 대한 링크가 있습니다. 이 같은. 그리고 GetSetMethodNormalizer가 올바르게 작동 할 것이라고 생각하십니까 (재귀 알고리즘 사용)?
Dmytro Krasun 2011

2
예 그것은 재귀 적이며 제 경우에는 내 문제였습니다. 따라서 특정 엔터티의 경우 CustomNormalizer 및 NormalizableInterface를 알고있는 것처럼 사용할 수 있습니다.
webda2l

2
내가 이것을 시도했을 때 "치명적인 오류 : 허용 된 메모리 크기 134217728 바이트가 소모되었습니다 (64 바이트 할당 시도)."/home/jason/pressbox/vendor/symfony/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php 라인 44 ". 왜 그런지 궁금 해요?
Jason Swett

1
시도했을 때 예외가 발생했습니다. 치명적 오류 : 최대 함수 중첩 수준 '100'에 도달했습니다. 중단 중! C : \ wamp \ www \ myapp \ application \ libraries \ doctrine \ Symfony \ Component \ Serializer \ Normalizer \ GetSetMethodNormalizer.php on line 223
user2350626 2013 년


11

답을 완성하기 위해 : 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
: 당신은 지금 내장 된 하나의 활성화로, 심포니 2.3의 시리얼 구성 요소를 사용하는 방법에 대한 요리 책 항목이있다 symfony.com/doc/current/cookbook/serializer.html
althaus

6

동일한 문제를 해결해야했습니다. 하나의 엔티티 ( "사용자")가 다른 엔티티 ( "위치")와 일대 다 양방향 연관성을 갖는 json 인코딩입니다.

여러 가지를 시도해 보았는데 이제 가장 적합한 솔루션을 찾은 것 같습니다. 아이디어는 David가 작성한 것과 동일한 코드를 사용하는 것이었지만 노멀 라이저에게 특정 지점에서 중지하도록 지시하여 어떻게 든 무한 재귀를 가로 챌 수 있습니다.

이 GetSetMethodNormalizer는 제 생각에 (반사 등에 기반한) 좋은 접근 방식이므로 사용자 지정 노멀 라이저를 구현하고 싶지 않았습니다. 그래서 속성 (isGetMethod)을 포함할지 여부를 말하는 메서드가 비공개이기 때문에 처음에는 사소하지 않은 하위 클래스로 결정했습니다.

그러나 정규화 메서드를 재정의 할 수 있으므로이 시점에서 "Location"을 참조하는 속성을 설정 해제하여 가로 채었습니다. 따라서 inifinite 루프가 중단됩니다.

코드에서는 다음과 같습니다.

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"를 비워 둘뿐만 아니라 잠재적으로 다른 Entites에 매핑되는 모든 Collections 유형 필드를 비워 둡니다. 즉, 재귀없이 직렬화하는 데 Ent에 대한 내부 / 고급 지식이 필요하지 않습니다.
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으로 seialize 할 수 있습니다.

  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. Doctrine 엔티티의 직렬화를위한 JMSSerializerBundle
  2. 응답보기 리스너를위한 FOSRestBundle 번들. 또한 컨트롤러 / 액션 이름을 기반으로 경로 정의를 생성 할 수 있습니다.
  3. NelmioApiDocBundle 은 온라인 문서 및 Sandbox (외부 도구없이 엔드 포인트를 테스트 할 수 있음) 를 자동 생성합니다.

모든 것을 올바르게 구성하면 엔티티 코드는 다음과 같습니다.

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 () 주석 사용).
  • 직렬화 그룹을 사용하면 다양한 상황에서 노출 된 필드를 제어 할 수 있습니다.
  • 컨트롤러는 매우 간단합니다. 액션 메서드는 엔티티 또는 엔티티 배열을 직접 반환 할 수 있으며 자동으로 직렬화됩니다.
  • 그리고 @ApiDoc ()을 사용하면 REST 클라이언트 또는 JavaScript 코드없이 브라우저에서 직접 엔드 포인트를 테스트 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.