วิธีเข้ารหัสเอนทิตีหลักคำสอนเป็น JSON ในแอปพลิเคชัน Symfony 2.0 AJAX


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 ฉันรู้ว่าฉันสามารถใช้ Normalizers และเข้ารหัสสิทธิ์ทั้งหมดได้ แต่จะเกิดอะไรขึ้นหากเอนทิตีวนรอบลิงก์ไปยังเอนทิตีอื่น หรือกราฟเอนทิตีใหญ่มาก? คุณมีข้อเสนอแนะใด?

ฉันคิดถึงสคีมาการเข้ารหัสสำหรับเอนทิตี ... หรือใช้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
คุณต้องเพิ่ม ใช้ JMS \ SerializerBundle \ Annotation \ excusionPolicy; ใช้ JMS \ SerializerBundle \ Annotation \ Exclude; ในเอนทิตีของคุณและติดตั้ง JMSSerializerBundle เพื่อให้ใช้งานได้
ioleo

3
ใช้งานได้ดีถ้าคุณเปลี่ยนเป็น: return new Response ($ reports);
Greywire

7
เนื่องจากคำอธิบายประกอบถูกย้ายออกจากบันเดิลข้อความการใช้งานที่ถูกต้องจึงมีดังนี้ใช้ JMS \ Serializer \ Annotation \ excusionPolicy; ใช้ JMS \ Serializer \ Annotation \ Exclude;
Pier-Luc Gendreau

3
เอกสารสำหรับหลักคำสอนกล่าวว่าอย่าทำให้วัตถุเป็นลำดับหรือทำให้เป็นอนุกรมด้วยความระมัดระวัง
Bluebaron

ฉันไม่จำเป็นต้องติดตั้ง JMSSerializerBundle ด้วยซ้ำ รหัสของคุณทำงานได้โดยไม่ต้องใช้ JMSSerializerBundle
Derk Jan Speelman

150

ด้วย 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);

3
นี่เป็นทางออกที่ยอดเยี่ยมหากคุณกำลังผูกมัดเพื่อให้การพึ่งพาของคุณกับกลุ่มอื่น ๆ ให้น้อยที่สุด ...
Drmjo

6
สิ่งที่เกี่ยวกับเอนทิตีที่เชื่อมโยง?
John the Ripper

8
สิ่งนี้ดูเหมือนจะใช้ไม่ได้กับการรวบรวมเอนทิตี (เช่น: OneToManyความสัมพันธ์)
Pierre de LESPINAY

2
สิ่งนี้ละเมิดหลักการความรับผิดชอบเดียวและไม่เป็นผลดีหากหน่วยงานของคุณสร้างขึ้นโดยอัตโนมัติโดยหลักคำสอน
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
ขอบคุณ แต่ฉันมีเอนทิตีผู้เล่นที่มีลิงก์ไปยังคอลเลกชันเอนทิตีเกมและเอนทิตีเกมทุกรายการมีลิงก์ไปยังผู้เล่นที่เล่นอยู่ อะไรทำนองนี้. คุณคิดว่า GetSetMethodNormalizer จะทำงานได้อย่างถูกต้องหรือไม่ (ใช้อัลกอริทึมแบบเรียกซ้ำ)
Dmytro Krasun

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 ในบรรทัด 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);

คุณยังสามารถกำหนดค่าให้ deserialize อาร์เรย์ในhttp://api.symfony.com/2.0


3
มีรายการตำราอาหารเกี่ยวกับการใช้ส่วนประกอบ Serializer ใน Symfony 2.3+ เนื่องจากคุณสามารถเปิดใช้งานในตัวได้แล้ว: symfony.com/doc/current/cookbook/serializer.html
althaus

6

ฉันต้องแก้ปัญหาเดียวกัน: json-encoding an entity ("User") ที่มี One-to-Many Bidirectional Association ไปยังเอนทิตีอื่น ("ที่ตั้ง")

ฉันลองทำหลายอย่างและคิดว่าตอนนี้ฉันพบวิธีแก้ปัญหาที่ดีที่สุดแล้ว แนวคิดคือใช้รหัสเดียวกับที่เขียนโดย David แต่อย่างใดก็ขัดขวางการเรียกซ้ำที่ไม่สิ้นสุดโดยบอกให้ Normalizer หยุดในบางจุด

ฉันไม่ต้องการใช้ Normalizer แบบกำหนดเองเนื่องจาก GetSetMethodNormalizer นี้เป็นแนวทางที่ดีในความคิดของฉัน (ขึ้นอยู่กับการสะท้อนแสงเป็นต้น) ดังนั้นฉันจึงตัดสินใจที่จะคลาสย่อยซึ่งไม่ใช่เรื่องเล็กน้อยตั้งแต่แรกเห็นเพราะวิธีการที่จะบอกว่าจะรวมคุณสมบัติ (isGetMethod) เป็นแบบส่วนตัวหรือไม่

แต่เราสามารถแทนที่เมธอด normalize ได้ดังนั้นฉันจึงสกัดกั้น ณ จุดนี้โดยเพียงแค่ยกเลิกการตั้งค่าคุณสมบัติที่อ้างถึง "ตำแหน่ง" - ดังนั้นการวนซ้ำแบบไม่สิ้นสุดจึงถูกขัดจังหวะ

ในโค้ดมีลักษณะดังนี้:

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. ไม่จำเป็นต้องแตะคลาสเอนทิตีใด ๆ 2. ไม่ใช่แค่เว้นว่าง "สถานที่" แต่ทุกฟิลด์ประเภทคอลเล็กชันซึ่งอาจจับคู่กับเอนทิตีอื่น ๆ กล่าวคือไม่จำเป็นต้องมีความรู้ภายใน / ล่วงหน้าของ 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);
}

ตัวอย่างของ 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.)
    );
}

}

ในคอนโทรลเลอร์:

$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.2

/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

เมื่อคุณต้องการสร้างจุดสิ้นสุด REST API จำนวนมากบน Symfony วิธีที่ดีที่สุดคือการใช้กลุ่มชุดต่อไปนี้:

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