Bagaimana cara menyandikan entitas Doktrin ke JSON di aplikasi Symfony 2.0 AJAX?

91

Saya sedang mengembangkan aplikasi game dan menggunakan Symfony 2.0. Saya memiliki banyak permintaan AJAX ke backend. Dan lebih banyak tanggapan yang mengubah entitas menjadi JSON. Sebagai contoh:

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

Dan semua pengontrol saya melakukan hal yang sama: dapatkan entitas dan menyandikan beberapa bidangnya ke JSON. Saya tahu bahwa saya dapat menggunakan normalizer dan menyandikan semua entitas. Tetapi bagaimana jika suatu entitas telah mengayunkan tautan ke entitas lain? Atau grafik entitas sangat besar? Apakah Anda punya saran?

Saya memikirkan tentang beberapa skema encoding untuk entitas ... atau menggunakan NormalizableInterfaceuntuk menghindari bersepeda ..,

Dmytro Krasun
sumber

Jawaban:

83

Pilihan lainnya adalah menggunakan JMSSerializerBundle . Di pengontrol Anda, Anda kemudian melakukannya

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

Anda dapat mengkonfigurasi bagaimana serialisasi dilakukan dengan menggunakan penjelasan di kelas entitas. Lihat dokumentasi di tautan di atas. Misalnya, berikut cara mengecualikan entitas tertaut:

 /**
* 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;
Sofia
sumber
7
Anda perlu menambahkan use JMS \ SerializerBundle \ Annotation \ ExclusionPolicy; gunakan JMS \ SerializerBundle \ Annotation \ Exclude; di entitas Anda dan instal JMSSerializerBundle agar ini berfungsi
ioleo
3
Berfungsi dengan baik jika Anda mengubahnya menjadi: return new Response ($ reports);
Greywire
7
Karena anotasi telah dipindahkan dari bundel, pernyataan penggunaan yang benar sekarang adalah: use JMS \ Serializer \ Annotation \ ExclusionPolicy; gunakan JMS \ Serializer \ Annotation \ Exclude;
Pier-Luc Gendreau
3
Dokumentasi untuk Doctrine mengatakan untuk tidak membuat serialisasi objek atau membuat serial dengan sangat hati-hati.
Bluebar
Saya bahkan tidak perlu menginstal JMSSerializerBundle. Kode Anda berfungsi tanpa memerlukan JMSSerializerBundle.
Derk Jan Speelman
149

Dengan php5.4 sekarang Anda dapat melakukan:

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

Dan kemudian panggil

json_encode(MyUserEntity);
SparSio
sumber
1
Saya sangat menyukai solusi ini!
Michael
3
Ini adalah solusi yang bagus jika Anda berusaha untuk meminimalkan ketergantungan Anda pada bundel lain ...
Drmjo
5
Bagaimana dengan entitas tertaut?
John the Ripper
7
Ini tampaknya tidak berfungsi dengan koleksi entitas (yaitu: OneToManyrelasi)
Pierre de LESPINAY
1
Ini melanggar asas tanggung jawab tunggal dan tidak ada gunanya jika entitas Anda dihasilkan secara otomatis oleh doktrin
jim smith
39

Anda dapat secara otomatis menyandikan ke Json, entitas kompleks Anda dengan:

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');
webda2l
sumber
3
Terima kasih, tetapi saya memiliki entitas Pemain yang memiliki tautan ke koleksi entitas Game dan setiap entitas Game memiliki tautan ke pemain yang bermain di dalamnya. Sesuatu seperti ini. Dan menurut Anda, apakah GetSetMethodNormalizer akan bekerja dengan benar (menggunakan algoritme rekursif)?
Dmytro Krasun
2
Ya itu rekursif dan itu masalah saya dalam kasus saya. Jadi, untuk entitas tertentu, Anda dapat menggunakan CustomNormalizer dan NormalizableInterface seperti yang Anda ketahui.
webda2l
2
Ketika saya mencoba ini, saya mendapat "Kesalahan fatal: Ukuran memori yang diizinkan sebesar 134217728 byte habis (mencoba mengalokasikan 64 byte) di /home/jason/pressbox/vendor/symfony/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php di baris 44 ". Kenapa ya?
Jason Swett
1
ketika saya mencoba, saya mendapat pengecualian di bawah .. Kesalahan fatal: Fungsi maksimum tingkat bersarang '100' tercapai, dibatalkan! di C: \ wamp \ www \ myapp \ application \ libraries \ doktrine \ Symfony \ Component \ Serializer \ Normalizer \ GetSetMethodNormalizer.php di baris 223
user2350626
1
@ user2350626, lihat stackoverflow.com/questions/4293775/…
webda2l
11

Untuk melengkapi jawabannya: Symfony2 hadir dengan pembungkus di sekitar json_encode: Symfony / Component / HttpFoundation / JsonResponse

Penggunaan umum di Pengontrol Anda:

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

Semoga ini membantu

J

jerome
sumber
10

Saya menemukan solusi untuk masalah serialisasi entitas adalah sebagai berikut:

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

di pengontrol saya:

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

contoh lain:

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

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

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

return new Response($json);

Anda bahkan dapat mengkonfigurasinya untuk deserialisasi array di http://api.symfony.com/2.0

rkmax
sumber
3
Ada entri buku masak tentang menggunakan komponen Serializer di Symfony 2.3 +, karena sekarang Anda dapat mengaktifkan built-in satu: symfony.com/doc/current/cookbook/serializer.html
Althaus
6

Saya hanya harus memecahkan masalah yang sama: json-encoding suatu entitas ("Pengguna") yang memiliki Asosiasi Dua Arah Satu-Ke-Banyak ke Entitas lain ("Lokasi").

Saya mencoba beberapa hal dan saya pikir sekarang saya menemukan solusi terbaik yang dapat diterima. Idenya adalah menggunakan kode yang sama seperti yang ditulis oleh David, tetapi entah bagaimana menghalangi rekursi tak terbatas dengan memberi tahu Normalizer untuk berhenti di beberapa titik.

Saya tidak ingin menerapkan normalizer khusus, karena GetSetMethodNormalizer ini adalah pendekatan yang bagus menurut saya (berdasarkan refleksi, dll.). Jadi saya telah memutuskan untuk membuat subkelasnya, yang pada pandangan pertama tidak sepele, karena metode untuk mengatakan apakah menyertakan properti (isGetMethod) adalah pribadi.

Tapi, seseorang dapat menimpa metode normalisasi, jadi saya memotong pada titik ini, dengan hanya membatalkan pengaturan properti yang mereferensikan "Lokasi" - sehingga loop tak terbatas terputus.

Dalam kode terlihat seperti ini:

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

} 
oxy
sumber
1
Saya bertanya-tanya betapa mudahnya untuk menggeneralisasi ini, sehingga 1. tidak perlu menyentuh kelas Entitas, 2. Tidak hanya mengosongkan "Lokasi", tetapi setiap bidang jenis Koleksi yang berpotensi memetakan ke Entitas lain. Yaitu tidak ada pengetahuan internal / awal tentang Ent yang diperlukan untuk membuat serialnya, bebas rekursi.
Marcos
6

Saya memiliki masalah yang sama dan saya memilih untuk membuat encoder saya sendiri, yang akan mengatasinya sendiri dengan rekursi.

Saya membuat kelas yang mengimplementasikan Symfony\Component\Serializer\Normalizer\NormalizerInterface, dan layanan yang menampung setiap 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);
}

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

}

Di pengontrol:

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

Kode lengkapnya ada di sini: https://github.com/progracqteur/WikiPedale/tree/master/src/Progracqteur/WikipedaleBundle/Resources/Normalizer

Julien Fastré
sumber
6

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

dan contoh untuk pengontrol Anda:

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

tetapi masalah dengan tipe bidang \ DateTime akan tetap ada.

Lebnik
sumber
6

Ini lebih merupakan pembaruan (untuk Symfony v: 2.7+ dan JmsSerializer v: 0.13. * @ Dev) , jadi untuk menghindari Jms mencoba memuat dan membuat serial seluruh grafik objek (atau dalam kasus hubungan siklik ..)

Model:

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

Inside an 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);
timmz
sumber
5

Jika Anda menggunakan Symfony 2.7 atau yang lebih baru , dan tidak ingin menyertakan bundel tambahan untuk serialisasi, mungkin Anda dapat mengikuti cara ini untuk menyegel entitas doktrin ke json -

  1. Dalam pengontrol (umum, induk) saya, saya memiliki fungsi yang mempersiapkan pembuat serial

    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. Kemudian gunakan untuk membuat Entitas menjadi JSON

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

Selesai!

Tetapi Anda mungkin perlu beberapa penyesuaian. Sebagai contoh -

Anis
sumber
4

Saat Anda perlu membuat banyak endpoint REST API di Symfony, cara terbaik adalah menggunakan tumpukan bundel berikut:

  1. JMSSerializerBundle untuk serialisasi entitas Doktrin
  2. Paket FOSRestBundle untuk pendengar tampilan respons. Juga dapat menghasilkan definisi rute berdasarkan nama pengontrol / tindakan.
  3. NelmioApiDocBundle untuk membuat dokumentasi online dan Sandbox secara otomatis (yang memungkinkan untuk menguji titik akhir tanpa alat eksternal).

Saat Anda mengonfigurasi semuanya dengan benar, kode entitas Anda akan terlihat seperti:

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

Kemudian, kode di pengontrol:

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

Manfaat dari pengaturan tersebut adalah:

  • Anotasi @JMS \ Expose () dalam entitas dapat ditambahkan ke bidang sederhana, dan ke semua jenis relasi. Juga ada kemungkinan untuk mengekspos hasil dari beberapa eksekusi metode (gunakan anotasi @JMS \ VirtualProperty () untuk itu)
  • Dengan grup serialisasi, kami dapat mengontrol bidang yang terbuka dalam situasi yang berbeda.
  • Pengontrolnya sangat sederhana. Metode tindakan dapat langsung mengembalikan entitas atau array entitas, dan mereka akan secara otomatis berseri.
  • Dan @ApiDoc () memungkinkan untuk menguji titik akhir langsung dari browser, tanpa klien REST atau kode JavaScript
Maksym Moskvychev
sumber
2

Sekarang Anda juga dapat menggunakan Doctrine ORM Transformations untuk mengonversi entitas menjadi array bersarang skalar dan sebaliknya

ScorpioT1000
sumber