Làm cách nào để mã hóa các thực thể Doctrine thành JSON trong ứng dụng Symfony 2.0 AJAX?


89

Tôi đang phát triển ứng dụng trò chơi và sử dụng Symfony 2.0. Tôi có nhiều yêu cầu AJAX đối với phần phụ trợ. Và nhiều phản hồi hơn đang chuyển đổi thực thể thành JSON. Ví dụ:

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

Và tất cả các bộ điều khiển của tôi đều làm điều tương tự: lấy một thực thể và mã hóa một số trường của nó thành JSON. Tôi biết rằng tôi có thể sử dụng trình chuẩn hóa và mã hóa tất cả các yêu cầu. Nhưng điều gì sẽ xảy ra nếu một thực thể có các liên kết theo chu kỳ đến thực thể khác? Hay biểu đồ thực thể rất lớn? Bạn có đề nghị nào không?

Tôi nghĩ về một số giản đồ mã hóa cho các thực thể ... hoặc sử dụng NormalizableInterfaceđể tránh lặp lại ..,

Câu trả lời:


82

Một tùy chọn khác là sử dụng JMSSerializerBundle . Trong bộ điều khiển của bạn sau đó bạn làm

$serializer = $this->container->get('serializer');
$reports = $serializer->serialize($doctrineobject, 'json');
return new Response($reports); // should be $reports as $doctrineobject is not serialized

Bạn có thể định cấu hình cách tuần tự hóa được thực hiện bằng cách sử dụng các chú thích trong lớp thực thể. Xem tài liệu trong liên kết ở trên. Ví dụ: đây là cách bạn loại trừ các thực thể được liên kết:

 /**
* 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
bạn cần thêm sử dụng JMS \ SerializerBundle \ Annotation \ ExclusivePolicy; sử dụng JMS \ SerializerBundle \ Annotation \ Loại trừ; trong thực thể của bạn và cài đặt JMSSerializerBundle để điều này hoạt động
ioleo

3
Hoạt động tốt nếu bạn thay đổi nó thành: trả lại Phản hồi mới ($ báo cáo);
Greywire

7
Vì các chú thích đã được chuyển ra khỏi gói, các câu lệnh sử dụng đúng bây giờ là: use JMS \ Serializer \ Annotation \ ExclusivePolicy; sử dụng JMS \ Serializer \ Annotation \ Exclude;
Pier-Luc Gendreau

3
Tài liệu cho Doctrine nói rằng không sắp xếp thứ tự các đối tượng hoặc sắp xếp thứ tự một cách cẩn thận.
Bluebaron

Tôi thậm chí không cần cài đặt JMSSerializerBundle. Mã của bạn đã hoạt động mà không yêu cầu JMSSerializerBundle.
Derk Jan Speelman

147

Với php5.4 bây giờ bạn có thể làm:

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

Và sau đó gọi

json_encode(MyUserEntity);

1
tôi thích giải pháp này rất nhiều!
Michael

3
Đây là một giải pháp tuyệt vời nếu bạn đang nhập để tiếp tục phụ thuộc vào bó khác ở mức tối thiểu ...
Drmjo

5
Điều gì về các thực thể được liên kết?
John the Ripper

7
Đây dường như không làm việc với các bộ sưu tập thực thể (ví dụ: OneToManycác mối quan hệ)
Pierre de LESPINAY

1
Điều này vi phạm các nguyên tắc trách nhiệm duy nhất và không tốt nếu tổ chức của bạn được tự động tạo ra bởi học thuyết
jim smith

39

Bạn có thể tự động mã hóa thành Json, thực thể phức tạp của bạn với:

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
Cảm ơn, nhưng tôi có thực thể Người chơi có liên kết đến bộ sưu tập thực thể Trò chơi và mọi thực thể Trò chơi đều có liên kết với những người chơi đã chơi trong đó. Một cái gì đó như thế này. Và bạn có nghĩ rằng GetSetMethodNormalizer sẽ hoạt động chính xác (nó sử dụng thuật toán đệ quy)?
Dmytro Krasun

2
Vâng, nó đệ quy và đó là vấn đề của tôi trong trường hợp của tôi. Vì vậy, đối với các thực thể cụ thể, bạn có thể sử dụng CustomNormalizer và NormalizableInterface của nó như bạn đã biết.
webda2l

2
Khi tôi thử điều này, tôi nhận được "Lỗi nghiêm trọng: Kích thước bộ nhớ được phép là 134217728 byte đã cạn kiệt (đã cố gắng phân bổ 64 byte) trong /home/jason/pressbox/vendor/symfony/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php bật dòng 44 ”. Tôi tự hỏi tại sao?
Jason Swett

1
khi tôi thử, tôi nhận được dưới đây ngoại lệ .. Lỗi nghiêm trọng: Đã đạt đến mức lồng hàm tối đa là '100', đang hủy bỏ! trong C: \ wamp \ www \ myapp \ ứng dụng \ thư viện \ học thuyết \ Symfony \ Component \ Serializer \ Normalizer \ GetSetMethodNormalizer.php trên dòng 223
user2350626


11

Để hoàn thành câu trả lời: Symfony2 đi kèm với một trình bao bọc xung quanh json_encode: Symfony / Component / HttpFoundation / JsonResponse

Cách sử dụng điển hình trong Bộ điều khiển của bạn:

...
use Symfony\Component\HttpFoundation\JsonResponse;
...
public function acmeAction() {
...
return new JsonResponse($array);
}

Hi vọng điêu nay co ich

J


10

Tôi đã tìm thấy giải pháp cho vấn đề tuần tự hóa các thực thể như sau:

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

trong bộ điều khiển của tôi:

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

ví dụ khác:

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

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

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

return new Response($json);

bạn thậm chí có thể định cấu hình nó để giải mã mảng trong http://api.symfony.com/2.0


3
Có một mục trong sách dạy nấu ăn về cách sử dụng thành phần Serializer trong Symfony 2.3+, vì bây giờ bạn có thể kích hoạt thành phần tích hợp sẵn: symfony.com/doc/current/cookbook/serializer.html
althaus

6

Tôi vừa phải giải quyết vấn đề tương tự: mã hóa json một thực thể ("Người dùng") có Liên kết hai chiều từ một đến nhiều với một thực thể khác ("Vị trí").

Tôi đã thử một số cách và tôi nghĩ bây giờ tôi đã tìm thấy giải pháp tốt nhất có thể chấp nhận được. Ý tưởng là sử dụng cùng một đoạn mã như được viết bởi David, nhưng bằng cách nào đó chặn được đệ quy vô hạn bằng cách yêu cầu Bộ chuẩn hóa dừng lại tại một số điểm.

Tôi không muốn triển khai trình chuẩn hóa tùy chỉnh, vì GetSetMethodNormalizer này theo ý kiến ​​của tôi là một cách tiếp cận tốt (dựa trên sự phản ánh, v.v.). Vì vậy, tôi đã quyết định phân lớp nó, điều này không hề nhỏ ngay từ cái nhìn đầu tiên, bởi vì phương thức để cho biết nếu bao gồm một thuộc tính (isGetMethod) là riêng tư.

Nhưng, người ta có thể ghi đè phương thức chuẩn hóa, vì vậy tôi đã chặn tại thời điểm này, bằng cách đơn giản bỏ thiết lập thuộc tính tham chiếu "Vị trí" - do đó, vòng lặp vô hạn bị gián đoạn.

Trong mã, nó trông như thế này:

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
Tôi tự hỏi sẽ dễ dàng để tổng quát hóa điều này như thế nào, để 1. không bao giờ cần phải chạm vào các lớp Thực thể, 2. Không chỉ để trống "Vị trí", mà mọi trường loại Bộ sưu tập có khả năng ánh xạ tới các Đối tượng khác. Tức là không có kiến ​​thức nội bộ / nâng cao về Ent được yêu cầu để tuần tự hóa nó, không có đệ quy.
Marcos

6

Tôi đã gặp vấn đề tương tự và tôi đã chọn tạo bộ mã hóa của riêng mình, bộ mã hóa này sẽ tự xử lý bằng đệ quy.

Tôi đã tạo các lớp triển khai Symfony\Component\Serializer\Normalizer\NormalizerInterfacevà một dịch vụ chứa mọi 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);
}

Ví dụ về Trình chuẩn hóa:

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

}

Trong bộ điều khiển:

$normalizerService = $this->get('normalizer.service'); //you will have to configure services.yml
$json = $normalizerService->serialize($myobject, 'json');
return new Response($json);

Mã hoàn chỉnh ở đây: https://github.com/progracqteur/WikiPedale/tree/master/src/Progracqteur/WikipedaleBundle/Resources/Normalizer


6

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

và ví dụ cho bộ điều khiển của bạn:

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

nhưng các vấn đề với loại trường \ DateTime sẽ vẫn còn.


6

Đây là bản cập nhật nhiều hơn (dành cho Symfony v: 2.7+ và JmsSerializer v: 0.13. * @ Dev ) , vì vậy để tránh việc Jms cố gắng tải và tuần tự hóa toàn bộ đồ thị đối tượng (hoặc trong trường hợp quan hệ tuần hoàn ..)

Mô hình:

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
      ......................
      ......................
   */

Bên trong một Hành động:

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

Nếu bạn đang sử dụng Symfony 2.7 trở lên và không muốn bao gồm bất kỳ gói bổ sung nào để tuần tự hóa, có thể bạn có thể làm theo cách này để tách các thực thể học thuyết thành json -

  1. Trong bộ điều khiển (chung, cha) của tôi, tôi có một chức năng chuẩn bị bộ nối tiếp

    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. Sau đó, sử dụng nó để tuần tự hóa các Thực thể thành JSON

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

Làm xong!

Nhưng bạn có thể cần một số tinh chỉnh. Ví dụ -


4

Khi bạn cần tạo nhiều điểm cuối API REST trên Symfony, cách tốt nhất là sử dụng chồng các gói sau:

  1. JMSSerializerBundle để tuần tự hóa các thực thể Doctrine
  2. Gói FOSRestBundle cho trình nghe chế độ xem phản hồi. Ngoài ra, nó có thể tạo định nghĩa các tuyến dựa trên tên bộ điều khiển / hành động.
  3. NelmioApiDocBundle để tự động tạo tài liệu trực tuyến và Hộp cát (cho phép kiểm tra điểm cuối mà không cần bất kỳ công cụ bên ngoài nào).

Khi bạn định cấu hình mọi thứ đúng cách, mã thực thể của bạn sẽ giống như sau:

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

Sau đó, mã trong bộ điều khiển:

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

Lợi ích của việc thiết lập như vậy là:

  • @JMS \ Expose () chú thích trong thực thể có thể được thêm vào các trường đơn giản và vào bất kỳ loại quan hệ nào. Ngoài ra, có khả năng hiển thị kết quả của một số thực thi phương thức (sử dụng chú thích @JMS \ VirtualProperty () cho điều đó)
  • Với các nhóm tuần tự hóa, chúng ta có thể kiểm soát các trường tiếp xúc trong các tình huống khác nhau.
  • Bộ điều khiển rất đơn giản. Phương thức hành động có thể trả về trực tiếp một thực thể hoặc một mảng các thực thể và chúng sẽ được tự động tuần tự hóa.
  • Và @ApiDoc () cho phép kiểm tra điểm cuối trực tiếp từ trình duyệt mà không cần bất kỳ ứng dụng khách REST hoặc mã JavaScript nào

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.