Symfony 2.0 AJAXアプリケーションでDoctrineエンティティをJSONにエンコードする方法は?


89

ゲームアプリを開発していて、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、循環を避けるために使用したりします。

回答:


82

別のオプションは、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 \ ExclusionPolicy使用して追加する必要があり ます。JMS \ SerializerBundle \ Annotation \ Excludeを使用します。これを機能させるには、エンティティにJMSSerializerBundleをインストールします
ioleo

3
これを次のように変更するとうまく機能します。return new Response($ reports);
Greywire 2013

7
注釈はバンドルから移動されたため、正しい使用ステートメントは次のとおりです。useJMS \ Serializer \ Annotation \ ExclusionPolicy; JMS \ Serializer \ Annotation \ Excludeを使用します。
Pier-Luc Gendreau 2013

3
Doctrineのドキュメントはオブジェクトをシリアライズしないか、細心の注意を払ってシリアライズしないように言っています。
Bluebaron 2013年

JMSSerializerBundleをインストールする必要すらありませんでした。コードはJMSSerializerBundleを必要とせずに機能しました。
Derk Jan Speelman

147

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
私はこのソリューションがとても好きです!
マイケル

3
これは、他のバンドルへの依存関係を最小限に抑えたい場合に最適なソリューションです
Drmjo

5
リンクされたエンティティはどうですか?
John the Ripper

7
これはエンティティコレクション(つまり、OneToMany関係)では機能しないようです
Pierre de LESPINAY

1
これは単一の責任の原則に違反しており、エンティティがドクトリンによって自動生成されている場合は適切ではありません
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 2011

2
はい、それは再帰的であり、それは私の場合私の問題でした。したがって、特定のエンティティでは、ご存知のようにCustomNormalizerとそのNormalizableInterfaceを使用できます。
webda2l 2011

2
これを試したところ、「致命的なエラー:/home/jason/pressbox/vendor/symfony/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php onで、メモリサイズが134217728バイトになりました(64バイトを割り当てようとしました)がなくなりました。ライン44 "。なんでかしら?
ジェイソン・スウェット

1
試してみたところ、例外が発生しました。C:\ wamp \ www \ myapp \ application \ libraries \ doctrine \ Symfony \ Component \ Serializer \ Normalizer \ GetSetMethodNormalizer.php in 223
user2350626

1
@ user2350626、stackoverflow.com
webda2l 2013年

11

答えを完了するには:Symfony2にはjson_encodeのラッパーが付属しています: Symfony / Component / HttpFoundation / JsonResponse

コントローラーの典型的な使用法:

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

お役に立てれば

J


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
Symfony 2.3以降でSerializerコンポーネントを使用することに関するクックブックエントリがあります。組み込みのものをアクティブ化できるようになりました:symfony.com/doc/current/cookbook/serializer.html
althaus

6

私は同じ問題を解決する必要がありました:json-encodingエンティティ( "User")と別のエンティティ( "Location")との1対多の双方向関連付けがあります。

私はいくつかのことを試しましたが、今、私は許容できる最良の解決策を見つけたと思います。アイデアは、Davidによって作成されたものと同じコードを使用することでしたが、ノーマライザにある時点で停止するように指示することにより、無限の再帰をインターセプトしました。

このGetSetMethodNormalizerは私の考えでは(リフレクションなどに基づいて)良いアプローチであるため、カスタムノーマライザーを実装したくありませんでした。プロパティを含めるかどうかを指定するメソッド(isGetMethod)がプライベートであるため、サブクラス化することにしました。

ただし、normalizeメソッドをオーバーライドできるので、この時点で、 "Location"を参照するプロパティの設定を解除するだけでインターセプトしました。これにより、無限ループが中断されます。

コードでは次のようになります。

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の内部/高度な知識は必要ありません。
マルコス

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以降を使用していて、シリアル化のための追加のバンドルを含めたくない場合は、この方法に従って、Doctrineエンティティを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

Symfonyで多くのREST APIエンドポイントを作成する必要がある場合、最善の方法は次のバンドルのスタックを使用することです:

  1. Doctrineエンティティのシリアル化のためのJMSSerializerBundle
  2. 応答ビューリスナーのFOSRestBundleバンドル。また、コントローラー/アクション名に基づいてルートの定義を生成できます。
  3. NelmioApiDocBundleは、オンラインドキュメントとサンドボックスを自動生成します(外部ツールなしでエンドポイントをテストできます)。

すべてを適切に構成すると、エンティティコードは次のようになります。

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.