FOSRestBundle + JMSSerializerBundleバンドルのSymfony REST APIについて一言

みなさんこんにちは! FOSRestBundle + JMSSerializerBundleでREST APIソリューションを構築することについて話しましょう。





ちょっとした話。



REST APIの開発への道は、約4年前に始まりました(私たちはGLAVVEBです )。 最初の試みは、Yiiフレームワークで独自の自転車を書くことでした。 わかった。 そして、将来、マイナーな修正を加えて、このソリューションをいくつかの小さなプロジェクトに適用しました。 私は自分の「自転車」のサードパーティではないため、以下のプロジェクトでは、Yiiフレームワークの落ち着いた拡張機能の1つを既に使用し、定期的に更新しています。



その後、Symfonyが当社に来ました。 Yiiで新しいプロジェクトは採用していません。 Symfonyに存在するRESTソリューションの検討を始めました。 もちろん、私たちが最初に実験を始めたのは、標準アセンブリFOSRestBundle + JMSSerializer(+ドキュメント生成用のNelmioApiDocBundle)でした。 誰かが興味を持っているなら、ドキュメントはこちらです。 私がここで気に入ったのは、コントローラーの魔法の欠如(モデルに基づいたルートの動的な生成とベースコントローラーのすべてのリクエストの処理)と、ドキュメントの生成の魔法の存在でした。



FOSRestBundle + JMSSerializerBundle



FOSRestBundle + JMSSerializerに基づくソリューションは、プロジェクトで非常に優れていることが証明されました。 ただし、自分のプロジェクトよりもプロジェクトを開発する前に、次の質問を決定する必要があります。



それぞれについて詳しく説明しましょう。



アクセス権管理システムを実装する方法は?



すぐに使用できるシンフォニーには、この点でいくつかの解決策があります。

-ACLを使用しますここで読むことができます

-役割+投票(投票者)に基づいてアクセス権を分離するシステムを編成しますこちらをご覧ください



私たち自身のために、2番目のオプションを選択しました。



リストフィルタリングを整理する方法



まず、おそらくほとんどの開発者と同様に、基本的なコントローラーを作成しました。 エンティティのフィルタリング、作成、更新のための基本的な方法を実装しました。 フィルタリングを実装するメソッドは、リクエストで渡されたパラメーターに基づいてクェリバルダーを動的に生成しました。 このコントローラーをプロジェクトからプロジェクトに移しました。 必要に応じてどこかで修正されました。 最終的に、さまざまなプロジェクトで、この基本的なコントローラーには大きな違いがありました。



次に、少し調整することにしました。 彼らは、特別なサービス、別のクラス(アクションパターン)でエンティティを追加および更新するロジックでフィルタリングを行いました。 そこでGlavwebRestBundleが生まれました。 その時、彼はこんな感じでした。



各アークの入れ子エンティティの境界を決定する方法は?



つまり、あるエンティティが他のエンティティのコレクションを含んでいる状況です。 この問題を解決するために、JMSSerializerには「MaxDepth」属性があり、基本的には次のようになります。



/** * @JMS\MaxDepth(depth=2) */ private $groups;
      
      







しかし、落とし穴があります。 深さは、エンティティに基づくものではなく、エンティティに起因するjsonオブジェクトの先頭から計算されます。 つまり オブジェクトがコレクションにネストされている場合、深さは3である必要があります。オブジェクトを1つのコピーで返す場合、depth = 2です。エンティティが互いにネストされている場合、 JMS \ MaxDepth(depth = 7) 。 以下に、MaxDepthを削除する方法を示します。



エンティティの特定のフィールドのみを返す方法は?



エンティティ「ユーザー」があるとします。ユーザーには、APIに表示したくないパスワードなど、いくつかのフィールドが含まれています。 ExclusionPolicy戦略とJMSSerializerのExpose属性は、これに役立ちます。



クラスに対して、ExclusionPolicy戦略を定義します。



 use JMS\Serializer\Annotation as JMS; /** * @JMS\ExclusionPolicy("all") */ class MedicalEscortType {
      
      







APIで必要なフィールドに指定するExposeは、残りのすべてがJMSSerializerによってスキップされます



  /** * @JMS\Expose * @var integer */ private $name;
      
      







リクエストに応じて特定のフィールドセットを返す方法は?



多くの場合、オブジェクトのリストには、限られたデータセットが必要です。特定のオブジェクトを表示するには、完全なオブジェクトが必要です。 これは、JMSSerializerの「グループ」属性を使用して実装できます。 各エンティティに対して、少なくとも2つのグループ、entity_listおよびentity_viewを定義しました。



スルーリクエストパラメーターを使用するコントローラーでは、必要な値を取得し、シリアライザーSerializerContextに渡します。



 $scopes = array_map('trim', explode(',', $request->get('_scope'))); $serializationContext = SerializationContext::create() ->setGroups(array_merge($scopes, [GroupsExclusionStrategy::DEFAULT_GROUP])) ; $view = $this->view($data, $statusCode, $headers); $view->setSerializationContext($serializationContext) return $view;
      
      







これにより、ネストの問題が解決され、フィールドにMaxDepthを指定する必要がなくなりました。 これで、クライアントはAPIを使用して、必要なネストを構成し、2セットのフィールド(リストまたはビュー)のいずれかを選択できます。



戻り値を変更する方法は?



ここでも、JMSSerializerが役に立ちます。リスナーを決定し、必要に応じてその中の出力を変更します。



 use JMS\Serializer\EventDispatcher\EventSubscriberInterface; use JMS\Serializer\EventDispatcher\ObjectEvent; use Vich\UploaderBundle\Templating\Helper\UploaderHelper; /** * Class SerializationListener * @package AppBundle\Listener */ class SerializationListener implements EventSubscriberInterface { /** * @var UploaderHelper */ private $uploaderHelper; /** * @param UploaderHelper $uploaderHelper */ public function __construct(UploaderHelper $uploaderHelper) { $this->uploaderHelper = $uploaderHelper; } /** * @inheritdoc */ static public function getSubscribedEvents() { return array( array('event' => 'serializer.post_serialize', 'class' => 'AppBundle\Entity\User', 'method' => 'onPostSerializeUserAvatar') ); } /** * @param ObjectEvent $event */ public function onPostSerializeUserAvatar(ObjectEvent $event) { $url = $this->uploaderHelper->asset($event->getObject(), 'avatarFile'); $event->getVisitor()->addData('avatarUrl', $url); }
      
      







PUTでファイルのアップロードを整理する方法は?



なぜなら PUTメソッドはフォームの送信を許可しません; POSTを使用してファイルを更新するか、base64でファイルをエンコードするオプションがありました。 どちらのオプションも私たちに合わなかった。 各フィールドの個別のapiリクエストを使用して、ファイルをアップロードおよび削除することにしました。 ユーザーに「アバター」フィールドがあるとします。したがって、2つの追加メソッドを実装する必要があります。POST/ api / user / {user} / avatarで新しいアバターをアップロード(1つのファイルフィールドでフォームを送信)およびDELETE / api / user / {user} /アバターは、既存のアバターを削除します。



REST APIをテストする方法は?



少なくとも私たちにとっては非常に重要な問題です。 ここには十分なニュアンスがありますが、次のいずれかの記事でそれらをより詳しく説明します。 つまり、AliceBundleと共にLiipFunctionalTestBundle +フィクスチャを使用しました。 そして、必要な機能を実装する独自のクラスを作成しました。 このコンポーネントはGlavwebRestBundleでも定義されていました



おわりに



実践が示しているように、FOSRestBundle + JMSSerializerソリューションは一般的に機能しています。 しかし、世界はますます多くの要件を要求しています。 これにより、SymfonyにREST APIを実装する概念を修正する必要がありました。 これについては次の記事で説明します。



All Articles