Doctrine varlıkları Symfony 2.0 AJAX uygulamasında JSON'a nasıl kodlanır?


91

Oyun uygulaması geliştiriyorum ve Symfony 2.0 kullanıyorum. Arka uca birçok AJAX isteğim var. Ve daha fazla yanıt, varlığı JSON'a dönüştürüyor. Örneğin:

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

Ve tüm denetleyicilerim aynı şeyi yapıyor: bir varlık alın ve bazı alanlarını JSON olarak kodlayın. Normalleştiriciler kullanabileceğimi ve tüm hakları kodlayabileceğimi biliyorum. Peki ya bir varlık diğer varlıkla döngüsel bağlara sahipse? Yoksa varlık grafiği çok mu büyük? Önerin var mı?

Varlıklar için bazı kodlama şemaları düşünüyorum ... veya NormalizableInterfacedöngüden kaçınmak için kullanıyor ..,

Yanıtlar:


83

Diğer bir seçenek de JMSSerializerBundle'ı kullanmaktır . Denetleyicinizde o zaman yaparsınız

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

Varlık sınıfındaki ek açıklamaları kullanarak serileştirmenin nasıl yapılacağını yapılandırabilirsiniz. Yukarıdaki bağlantıdaki belgelere bakın. Örneğin, bağlantılı varlıkları şu şekilde hariç tutabilirsiniz:

 /**
* 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 kullanımı eklemeniz gerekir ; JMS \ SerializerBundle \ Annotation \ Exclude kullanın; varlığınızda ve bunun çalışması için JMSSerializerBundle'ı yükleyin
ioleo

3
Şu şekilde değiştirirseniz harika çalışır: yeni Yanıt döndür ($ raporlar);
Greywire

7
Ek açıklamalar paketin dışına taşındığı için artık doğru kullanım ifadeleri şunlardır: JMS \ Serializer \ Annotation \ ExclusionPolicy kullanın; JMS \ Serializer \ Annotation \ Exclude kullanın;
Pier-Luc Gendreau

3
Doctrine dokümantasyonu nesneleri serileştirmemeyi veya büyük bir özenle serileştirmemeyi söylüyor.
Bluebaron

JMSSerializerBundle'ı yüklememe bile gerek kalmadı. Kodunuz JMSSerializerBundle gerektirmeden çalıştı.
Derk Jan Speelman

149

Php5.4 ile artık şunları yapabilirsiniz:

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

Ve sonra ara

json_encode(MyUserEntity);

1
Bu çözümü çok beğendim!
Michael

3
Diğer paketlere olan bağımlılıklarınızı minimumda tutmaya çalışıyorsanız, bu harika bir çözüm ...
Drmjo

6
Ya bağlantılı varlıklar?
Karındeşen John

8
Bu, varlık koleksiyonlarıyla (yani: OneToManyilişkiler) işe yaramıyor gibi görünüyor
Pierre de LESPINAY

2
Bu, tek sorumluluk ilkesini ihlal eder ve varlıklarınız doktrin tarafından otomatik olarak oluşturulmuşsa
jim smith

39

Aşağıdakilerle karmaşık varlığınız olan Json'a otomatik olarak kodlayabilirsiniz:

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
Teşekkürler, ama Oyun varlıkları koleksiyonuna bağlantısı olan Oyuncu varlığım var ve her Oyun varlığının içinde oynayan oyuncularla bağlantısı var. Bunun gibi bir şey. GetSetMethodNormalizer'ın doğru çalışacağını düşünüyor musunuz (özyinelemeli algoritma kullanır)?
Dmytro Krasun

2
Evet yinelemeli ve benim durumumdaki sorunum buydu. Böylece, belirli varlıklar için, bildiğiniz gibi CustomNormalizer'ı ve onun NormalizableInterface'ini kullanabilirsiniz.
webda2l

2
Bunu denediğimde, /home/jason/pressbox/vendor/symfony/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php dosyasında "Önemli hata: İzin verilen 134217728 bayt bellek boyutu tükendi (64 bayt ayırmaya çalıştı) aldım satır 44 ". Nedenini merak ediyorum?
Jason Swett

1
Denediğimde istisnanın altına düştüm .. Önemli hata: Maksimum işlev iç içe geçme düzeyi '100'e ulaşıldı, iptal ediliyor! C: \ wamp \ www \ myapp \ application \ libraries \ doctrine \ Symfony \ Component \ Serializer \ Normalizer \ GetSetMethodNormalizer.php satırında 223
user2350626


11

Cevabı tamamlamak için: Symfony2, json_encode etrafında bir sarmalayıcıyla birlikte gelir: Symfony / Component / HttpFoundation / JsonResponse

Denetleyicilerinizde tipik kullanım:

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

Bu yardımcı olur umarım

J


10

Varlıkları serileştirme sorununun çözümünü şu şekilde buldum:

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

denetleyicimde:

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

diğer örnek:

$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'da dizileri seriyi kaldırmak için bile yapılandırabilirsiniz


3
Symfony 2.3+ sürümünde
2014

6

Ben de aynı sorunu çözmem gerekiyordu: Bire Çok İki Yönlü İlişkilendirme ("Konum") olan bir varlığı ("Kullanıcı") json olarak kodlamak.

Birkaç şeyi denedim ve sanırım şimdi kabul edilebilir en iyi çözümü buldum. Fikir, David tarafından yazılanla aynı kodu kullanmaktı, ancak bir şekilde Normalizer'a bir noktada durmasını söyleyerek sonsuz özyinelemeyi durdurmaktı.

Özel bir normalleştirici uygulamak istemedim, çünkü bu GetSetMethodNormalizer bence güzel bir yaklaşım (yansıma vb.). Bu yüzden onu alt sınıfa ayırmaya karar verdim, ki bu ilk bakışta önemsiz değildir, çünkü bir özelliği (isGetMethod) dahil edip etmeyeceğini söyleme yöntemi özeldir.

Ancak, normalize yöntemi geçersiz kılınabilir, bu yüzden bu noktada, sadece "Konum" a gönderme yapan özelliği kaldırarak müdahale ettim - böylece sonsuz döngü kesintiye uğrar.

Kodda şöyle görünür:

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
Bunu genellemenin ne kadar kolay olacağını merak ediyorum, böylece 1. Varlık sınıflarına dokunmaya gerek yok, 2. Sadece "Konumlar" ı değil, potansiyel olarak diğer Varlıklarla eşleşen her Koleksiyon türü alanını da boşaltın. Yinelemesiz, onu serileştirmek için Ent'in dahili / ileri bilgisi gerekmez.
Marcos

6

Ben de aynı sorunu yaşadım ve özyineleme ile kendi başlarına başa çıkacak olan kendi kodlayıcımı yaratmayı seçtim.

Uygulayan sınıflar Symfony\Component\Serializer\Normalizer\NormalizerInterfaceve her birini tutan bir hizmet yarattım 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);
}

Normalleştiriciye bir örnek:

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

}

Bir kontrolörde:

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

Kodun tamamı burada: https://github.com/progracqteur/WikiPedale/tree/master/src/Progracqteur/WikipedaleBundle/Resources/Normalizer


6

Symfony 2.3'te

/app/config/config.yml

framework:
    # сервис конвертирования объектов в массивы, json, xml и обратно
    serializer:
        enabled: true

services:
    object_normalizer:
        class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
        tags:
        # помечаем к чему относится этот сервис, это оч. важно, т.к. иначе работать не будет
          - { name: serializer.normalizer }

ve denetleyiciniz için örnek:

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

ancak \ DateTime alan türü ile ilgili sorunlar kalacaktır.


6

Bu daha çok bir güncellemedir (Symfony v: 2.7+ ve JmsSerializer v: 0.13. * @ Dev için) , bu nedenle Jms'in tüm nesne grafiğini yüklemeye ve serileştirmeye çalışmasından kaçınmak için (veya döngüsel ilişki durumunda ..)

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

Eylem İçinde:

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 veya üstünü kullanıyorsanız ve serileştirme için herhangi bir ek paket eklemek istemiyorsanız, belki de doktrin varlıklarını json olarak ele geçirmek için bu yolu takip edebilirsiniz -

  1. (Ortak, üst) denetleyicimde, serileştiriciyi hazırlayan bir işleve sahibim

    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. Ardından Varlıkları JSON'a serileştirmek için kullanın

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

Bitti!

Ancak biraz ince ayar yapmanız gerekebilir. Örneğin -


4

Symfony'de çok sayıda REST API uç noktası oluşturmanız gerektiğinde, en iyi yol aşağıdaki paket yığınlarını kullanmaktır:

  1. Doctrine varlıklarının serileştirilmesi için JMSSerializerBundle
  2. Yanıt görüntüleme dinleyicisi için FOSRestBundle paketi. Ayrıca denetleyici / eylem adına göre yol tanımları oluşturabilir.
  3. NelmioApiDocBundle , çevrimiçi dokümantasyonu ve Sandbox'ı (herhangi bir harici araç olmadan uç noktayı test etmeye izin verir) otomatik olarak oluşturur.

Her şeyi doğru bir şekilde yapılandırdığınızda, varlık kodunuz şöyle görünecektir:

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

Ardından, denetleyicide kodlayı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();
    }
}

Böyle bir kurulumun faydaları şunlardır:

  • Varlıktaki @JMS \ Expose () ek açıklamaları basit alanlara ve herhangi bir ilişki türüne eklenebilir. Ayrıca, bazı yöntem yürütme sonuçlarının ortaya çıkması olasılığı vardır (bunun için @ JMS \ VirtualProperty () ek açıklamasını kullanın)
  • Serileştirme grupları ile farklı durumlarda açık alanları kontrol edebiliriz.
  • Kontrolörler çok basit. Eylem yöntemi doğrudan bir varlık veya varlık dizisi döndürebilir ve otomatik olarak serileştirilir.
  • Ve @ApiDoc (), herhangi bir REST istemcisi veya JavaScript kodu olmadan uç noktayı doğrudan tarayıcıdan test etmeye izin verir

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.