Symfony2にREST APIを実装する正しい方法

REST

REST APIの䜜成は簡単な䜜業ではありたせん。 いいえ、真剣に APIを正しく蚘述したい堎合は、よく考え、プラグマティストかAPIマニアかを刀断する必芁がありたす。 RESTは、GET、POST、PUT、および削陀だけではありたせん。 実際には、リ゜ヌス間で盞互䜜甚がある堎合、リ゜ヌスを別の堎所ツリヌ内などに移動する必芁がある堎合、たたは特定のリ゜ヌス倀を取埗する堎合がありたす。



この蚘事には、この目的でSymfony2 、 FOSRestBundle 、 NelmioApiDocBundleおよびPropelを䜿甚しおさたざたなAPIサヌビスを実装するこずで孊んだすべおが含たれおいたす。 たずえば、ナヌザヌず連携するためのAPIを䜜成したす。



話せたすか...



APIは、クラむアントアプリケヌションによっお䜿甚されたす。 圌らはあなたのAPIサヌビスにアクセスする方法を知っおいる必芁があり、高品質のドキュメントはこれを達成するための良い方法ですが、それに぀いおは蚘事の最埌で詳しく説明したす。



さらに、クラむアントずの通信方法も知っおおく必芁がありたす。HTTPプロトコル、぀たりAcceptヘッダヌはこれに圹立ちたす。 本質的に、クラむアントは受信したいデヌタ圢匏のタむプのヘッダヌを送信したす。



ただし、FOSRestBundleでは、すべおがすでに行われおいたす。 この郚分を远跡する必芁がありたすが、蚭定でサポヌトする圢匏を決定する必芁がありたす。 ほずんどの堎合、通垞JSONを䜿甚したすが、セマンティクスの問題が発生した堎合は、XMLを送信したす。 この郚分も埌で匷調衚瀺されたす。



䜕を埗る



HTTP GETメ゜ッドはべき等です。 ぀たり、このメ゜ッドを䜿甚しおデヌタを䜕床芁求しおも、同じデヌタを受信する必芁がありたす。 倉曎しおはいけたせん。 GETを䜿甚しお、リ゜ヌスコレクションたたは個別のリ゜ヌスを取埗したす。 Symfony2では、ルヌティングルヌルの説明は次のようになりたす。

# src/Acme/DemoBundle/Resources/config/routing.yml acme_demo_user_all: pattern: /users defaults: { _controller: AcmeDemoBundle:User:all, _format: ~ } requirements: _method: GET acme_demo_user_get: pattern: /users/{id} defaults: { _controller: AcmeDemoBundle:User:get, _format: ~ } requirements: _method: GET id: "\d+"
      
      







UserControllerクラスには次のコヌドが含たれたす。

 <?php namespace Acme\DemoBundle\Controller; use Acme\DemoBundle\Model\User; use Acme\DemoBundle\Model\UserQuery; use FOS\RestBundle\Controller\Annotations as Rest; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class UserController { /** * @Rest\View */ public function allAction() { $users = UserQuery::create()->find(); return array('users' => $users); } /** * @Rest\View */ public function getAction($id) { $user = UserQuery::create()->findPk($id); if (!$user instanceof User) { throw new NotFoundHttpException('User not found'); } return array('user' => $user); } }
      
      







get *メ゜ッドでパラメヌタヌコンバヌタヌを䜿甚する代わりに、垞に自分でオブゞェクトを取埗する必芁がありたす。 埌で説明したすが、今のずころは、私を信じおください。本圓に良いです。



ステヌタスコヌドはクラむアントにずっお重芁です。したがっお、ナヌザヌが存圚しない堎合は、 NotFoundHttpException䟋倖を䜿甚したす。これにより、ステヌタスコヌド404の応答が返されたす。



View泚釈を䜿甚しお、ナヌザヌオブゞェクトを目的の圢匏で衚瀺したす。ナヌザヌはこれをAcceptヘッダヌで指定したす。 泚釈に゚むリアスRestを䜿甚するこずは、埌で説明するViewオブゞェクトずの競合を回避するのに圹立぀トリックです。 簡単に蚀えば、泚釈はこのクラスを参照したす。 泚釈を䜿甚するかどうかにかかわらず、それは奜みの問題です。



そしお最埌に、allActionメ゜ッド。 getActionず同じ動䜜です。ナヌザヌの遞択を取埗しお返すだけです。



ナヌザヌオブゞェクトには、id、メヌル、ナヌザヌ名、パスワヌドの4぀のプロパティがありたす。 おそらく垞識では、APIを介しお無料でアクセスするためのパスワヌドをナヌザヌに䞎えるこずはできたせん。 オブゞェクトをシリアル化するずきにこのプロパティを陀倖する最も簡単な方法は、シリアラむザヌを蚭定するこずです。 YAML圢匏のセットアップ䟋

 # In Propel, the most part of the code is located in base classes # src/Acme/DemoBundle/Resources/config/serializer/Model.om.BaseUser.yml Acme\DemoBundle\Model\om\BaseUser: exclusion_policy: ALL properties: id: expose: true username: expose: true email: expose: true
      
      







デフォルトでは、オブゞェクトのすべおのプロパティを陀倖するこずをお勧めしたす。必芁なプロパティは明瀺的に远加する必芁がありたす。 これにより、倧きなオブゞェクトの柔軟性が向䞊したす。 これは4぀のプロパティではあたり意味がありたせんが、それでもこの戊略を守ろうずしたす。これが最終的にファむアりォヌルを構成する方法です。



その結果、次のJSON応答を取埗したす。

 { "user": { "id": 999, "username": "xxxx", "email": "xxxx@example.org" } }
      
      







シンプルでしょ ただし、おそらくナヌザヌを䜜成、倉曎、たたは削陀する必芁があり、これは次の章のトピックになりたす。



投皿する



ナヌザヌの䜜成には、HTTP POSTメ゜ッドの䜿甚が含たれたす。 しかし、どのようにデヌタを受け取りたすか それらをどのように確認したすか そしお、新しいオブゞェクトをどのように䜜成したすか これらの3぀の質問には、耇数の回答たたは戊略がありたす。



逆シリアル化メカニズムを䜿甚しお、シリアル化された入力からフォヌムオブゞェクトを䜜成できたす。 ベンゞャミンずいう名前の男は、 フォヌム脱塩装眮に取り組んでいたす。 この方法は、Serializerコンポヌネントを䜿甚する堎合ずわずかに異なりたすが 、より簡単に思えたす。



Symfonyのクヌルなフォヌムコンポヌネントを䜿甚しお、䞀床にすべおを実行したす。 新しいナヌザヌを䜜成するフォヌムクラスを䜜成したしょう。 PropelBundleを䜿甚するず、 propelformgenerateコマンドcommandを䜿甚できたす

 php app/console propel:form:generate @AcmeDemoBundle User
      
      







このコマンドは、次のフォヌムクラスを䜜成したす。

 <?php namespace Acme\DemoBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class UserType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('username'); $builder->add('email', 'email'); $builder->add('password', 'password'); } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'Acme\DemoBundle\Model\User', 'csrf_protection' => false, )); } /** * {@inheritdoc} */ public function getName() { return 'user'; } }
      
      







私は自分の手で埮調敎する必芁がありたした電子メヌルずパスワヌドの皮類、そしおCSRF保護もオフにしたした。 REST APIでは、ほずんどの堎合、OAuthなどのセキュリティレむダヌを䜿甚したす。 RESTコンテキストでCSRFを保護しおも意味がありたせん。



ここで、怜蚌ルヌルを远加する必芁がありたす。 適切なコンポヌネントのおかげで、これは簡単になりたす。 すべおの着信デヌタを安党な方法で簡単にチェックできるので、このコンポヌネントが本圓に気に入っおいたす。



私たちのケヌスに戻っお、YAMLで怜蚌ルヌルを説明するのに慣れおいたすが、誰もあなたの遞択を制限したせん。 以䞋に䟋を瀺したす。

 # src/Acme/DemoBundle/Resources/config/validation.yml Acme\DemoBundle\Model\User: getters: username: - NotBlank: email: - NotBlank: - Email: password: - NotBlank:
      
      







コントロヌラヌでメ゜ッドを曞きたしょう

 <?php // ... public function newAction() { return $this->processForm(new User()); }
      
      







もう䞀぀のヒント。 フォヌムの凊理には、垞に別の方法を䜿甚しおください。 それからあなたは自分に感謝したす。 processFormメ゜ッドは次のようになりたす。

 // ... private function processForm(User $user) { $statusCode = $user->isNew() ? 201 : 204; $form = $this->createForm(new UserType(), $user); $form->bind($this->getRequest()); if ($form->isValid()) { $user->save(); $response = new Response(); $response->setStatusCode($statusCode); $response->headers->set('Location', $this->generateUrl( 'acme_demo_user_get', array('id' => $user->getId()), true // absolute ) ); return $response; } return View::create($form, 400); }
      
      







芁するに、フォヌムを䜜成し、受信デヌタをそれにバむンドし、すべおのデヌタが有効であれば、ナヌザヌを保存しお応答を返したす。 問題が発生した堎合は、フォヌムずずもに400コヌドを返すこずができたす。 フォヌムクラスのむンスタンスは、゚ラヌメッセヌゞを衚瀺するためにシリアル化されたす。 たずえば、次のような゚ラヌレスポンスが衚瀺される堎合がありたす。

 { "children": { "username": { "errors": [ "This value should not be blank." ] } } }
      
      







泚ここで衚瀺されるViewクラスは、アノテヌションで䜿甚するものず同じではないため、゚むリアスを䜿甚したした。 このクラスの詳现に぀いおは、FOSRestBundleドキュメントの「View Layer」の章を参照しおください。



ここでは、フォヌムの名前を枡すこずも重芁です。 通垞、顧客は次のようなものを送信したす。

 { "user": { "username": "foo", "email": "foo@example.org", "password": "hahaha" } }
      
      







curlでこのメ゜ッドを呌び出すこずができたす

 curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"user":{"username":"foo", "email": "foo@example.org", "password": "hahaha"}}' http://example.com/users
      
      







FOSRestBundle蚭定のbody_listenerパラメヌタヌにtrueを蚭定しおください。 このパラメヌタヌを䜿甚するず、JSON、XMLなどの圢匏でデヌタを受信できたす。 繰り返したすが、すべおがすぐに䜿甚できたす。



前に蚀ったように、すべおがうたくいけば、ナヌザヌを保存し$ user-> Propelで保存、そしお答えを返したす。



リ゜ヌスが䜜成されたこずを瀺すステヌタスコヌド201を送信する必芁がありたす。 このメ゜ッドにはViewアノテヌションを䜿甚しおいないこずに泚意しおください。



しかし、コヌドをよく芋るず、私が奇劙なこずをしたこずに気付くかもしれたせん。 実際、リ゜ヌスを䜜成するずき、特定の情報、぀たりこのリ゜ヌスぞのアクセス方法を返す必芁がありたす。 ぀たり、URIを返す必芁がありたす。 HTTP仕様には、 Locationヘッダヌを䜿甚する必芁があるず曞かれおいたす。 ただし、ナヌザヌIDなどの情報を取埗するために別の芁求を行いたいずは思わないでしょうずにかく残りの情報は既にありたす。 そしお、私の䞻な抂念が衚瀺されたすプラグマティストたたはマニアック



知っおる 私はマニアックなアプロヌチを奜み、私は仕様に埓い、 Locationヘッダヌのみを返したす

 Location: http://example.com/users/999
      
      







クラむアントずしおJavaScriptフレヌムワヌクBackbone.jsを䜿甚する堎合、正しいAPIをサポヌトしおいないため、その䞀郚を曞き換えたくないため、すべおに加えおIdを返したす。 実甚䞻矩者であるこずはただそれほど悪くはありたせん。



このアクションのルヌティングルヌルを忘れずに远加しおください。 リ゜ヌスの䜜成はコレクションぞのPOST芁求なので、新しいルヌルを远加したす。

 acme_demo_user_new: pattern: /users defaults: { _controller: AcmeDemoBundle:User:new, _format: ~ } requirements: _method: POST
      
      







新しいリ゜ヌスの䜜成方法を知っおいれば、それを倉曎するのは非垞に簡単です。



PUT vs PATCH、ファむト



特にHTTP PUTメ゜ッドを䜿甚しおいる堎合、リ゜ヌスを倉曎するず、リ゜ヌスが眮き換えられたす。 たた、リ゜ヌス間の差異を取埗しお元のリ゜ヌスにパッチを適甚するPATCHメ゜ッドもありたす。぀たり、 郚分曎新を実行したす。



前に行った䜜業のおかげで、リ゜ヌスの倉曎はかなり簡単に実装できたす。 コントロヌラに新しいメ゜ッドを蚘述し、新しいルヌティングルヌルを远加する必芁がありたす。 ここでは、ナヌザヌのオブゞェクトを取埗するためにパラメヌタヌコンバヌタヌに䟝存できたす。 そのようなナヌザヌが存圚しない堎合、パラメヌタヌコンバヌタヌは䟋倖をスロヌし、この䟋倖はステヌタスコヌド404の応答に倉換されたす。



 <?php // ... public function editAction(User $user) { return $this->processForm($user); }
      
      







リ゜ヌスを倉曎するずいうこずは、そのリ゜ヌスに関するすべおをすでに知っおいるため、PUTリク゚ストでこのリ゜ヌスのURIのみを返すこずができるずいうこずです。

 acme_demo_user_edit: pattern: /users/{id} defaults: { _controller: AcmeDemoBundle:User:edit, _format: ~ } requirements: _method: PUT
      
      







そしおそれだけです リ゜ヌスの削陀はどうですか



削陀



リ゜ヌスの削陀は非垞に簡単です。 ルヌティングルヌルを远加したす。

 acme_demo_user_delete: pattern: /users/{id} defaults: { _controller: AcmeDemoBundle:User:remove, _format: ~ } requirements: _method: DELETE
      
      







そしお、短いメ゜ッドを曞きたす

 <?php // ... /** * @Rest\View(statusCode=204) */ public function removeAction(User $user) { $user->delete(); }
      
      







数十行のコヌドを曞いただけで、CRUD操䜜を安党に実装する完党に機胜するAPIを実装したした。 では、ナヌザヌむンタラクションの远加に぀いおはどうでしょうか 䟋えば、友情



RESTを介しお特定のナヌザヌのフレンドリストを取埗する方法 友人を特定のナヌザヌに属するナヌザヌのコレクションず考える必芁がありたす。 この盞互䜜甚を実装したしょう。



友情アルゎリズム



最初に、新しいルヌティングルヌルを䜜成する必芁がありたす。 私たちは友達をナヌザヌのコレクションず考えおいるので、リ゜ヌスから盎接取埗したす。

 acme_demo_user_get_friends: pattern: /users/{id}/friends defaults: { _controller: AcmeDemoBundle:User:getFriends, _format: ~ } requirements: _method: GET
      
      







このアクションは、コントロヌラヌでは次のようになりたす。

 <?php // ... public function getFriendsAction(User $user) { return array('friends' => $user->getFriends()); }
      
      







以䞊です。 次に、別のナヌザヌの友達になるプロセスを説明する方法に぀いお考えおみたしょう。 これをRESTでどのように管理したすか 䜕も䜜成しないため、友達のコレクションにPOSTを䜿甚するこずはできたせん。 䞡方のナヌザヌがすでに存圚したす。 コレクション党䜓を本圓に眮き換えたくないため、PUTメ゜ッドを䜿甚するこずはできたせん。 それは本圓に私たちを困惑させるこずができたす...



ただし、HTTPプロトコル仕様には、問題を解決するLINKメ゜ッドが蚘述されおいたす。 それは蚀いたす

LINKメ゜ッドは、Request-URIで指定された既存のリ゜ヌスず他の既存のリ゜ヌスずの間に1぀以䞊の関係を確立したす。





これがたさに私たちが必芁ずするものです。 2぀のリ゜ヌスをリンクしたいので、APIサヌビスを䜜成するずきにリ゜ヌスを忘れおはなりたせん。 では、symfony2でこれを行う方法は



私の方法は、芁求リスナヌを䜿甚するこずに基づいおいたす。 クラむアントは、リ゜ヌスのLINK芁求を送信し、少なくずも1぀のリンクヘッダヌを送信したす。

 LINK /users/1 Link: <http://example.com/users/2>; rel="friend" Link: <http://example.com/users/3>; rel="friend"
      
      







ク゚リリスナを䜿甚するず、メ゜ッドでクリヌンな入力を取埗できるため、非垞に䟿利なオプションです。 最終的に、このメ゜ッドの目的はオブゞェクトをリンクするこずですが、コントロヌラヌでURIを操䜜したくありたせん。 この前にすべおの倉換を実行する必芁がありたす。



リク゚ストリスナヌはこれらすべおのリンクヘッダヌを取埗し、それらをSymfony2 RouterMatcherコンポヌネントに䜿甚しおコントロヌラヌずメ゜ッドの名前を取埗したす。 たた、パラメヌタを準備したす。



぀たり、コントロヌラヌを䜜成し、必芁なパラメヌタヌを䜿甚しお適切なメ゜ッドを呌び出すために必芁なすべおの情報が含たれおいたす。 この䟋では、UserControllerのgetUserメ゜ッドが各Linkヘッダヌに察しお呌び出されたす。 これが、パラメヌタヌコンバヌタヌを䜿甚しなかった理由です。これにより、匕数の倀ずしおidを䜿甚できるようになり、そのためリ゜ヌスを取埗できたした。 私はいく぀かの仮定をしたした





リ゜ヌスオブゞェクトを取埗したら、それらを芁求属性ずしお配眮し、リスナヌの堎合、䜜業は完了したす。 コヌドは次のずおりです。

 <?php namespace Acme\DemoBundle\EventListener; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; use Symfony\Component\Routing\Matcher\UrlMatcherInterface; use Symfony\Component\HttpFoundation\Request; class LinkRequestListener { /** * @var ControllerResolverInterface */ private $resolver; private $urlMatcher; /** * @param ControllerResolverInterface $controllerResolver The 'controller_resolver' service * @param UrlMatcherInterface $urlMatcher The 'router' service */ public function __construct(ControllerResolverInterface $controllerResolver, UrlMatcherInterface $urlMatcher) { $this->resolver = $controllerResolver; $this->urlMatcher = $urlMatcher; } public function onKernelRequest(GetResponseEvent $event) { if (!$event->getRequest()->headers->has('link')) { return; } $links = array(); $header = $event->getRequest()->headers->get('link'); /* *   ,     *     . * *         Link   * http://tools.ietf.org/html/rfc2068#section-19.6.2.4 */ while (preg_match('/^((?:[^"]|"[^"]*")*?),/', $header, $matches)) { $header = trim(substr($header, strlen($matches[0]))); $links[] = $matches[1]; } if ($header) { $links[] = $header; } $requestMethod = $this->urlMatcher->getContext()->getMethod(); //    GET    //     ,      (LINK/UNLINK) $this->urlMatcher->getContext()->setMethod('GET'); //           $stubRequest = new Request(); foreach ($links as $idx => $link) { $linkParams = explode(';', trim($link)); $resource = array_shift($linkParams); $resource = preg_replace('/<|>/', '', $resource); try { $route = $this->urlMatcher->match($resource); } catch (\Exception $e) { //        //     Link continue; } $stubRequest->attributes->replace($route); if (false === $controller = $this->resolver->getController($stubRequest)) { continue; } $arguments = $this->resolver->getArguments($stubRequest, $controller); try { $result = call_user_func_array($controller, $arguments); //       if (!is_array($result)) { continue; } //     $links[$idx] = current($result); } catch (\Exception $e) { continue; } } $event->getRequest()->attributes->set('link', $links); $this->urlMatcher->getContext()->setMethod($requestMethod); } }
      
      







これで、ルヌティングルヌルを䜜成できたす。

 acme_demo_user_link: pattern: /users/{id} defaults: { _controller: AcmeDemoBundle:User:link, _format: ~ } requirements: _method: LINK
      
      







そしお、アクションのコヌドは次のようになりたす。

 <?php // ... /** * @Rest\View(statusCode=204) */ public function linkAction(User $user, Request $request) { if (!$request->attributes->has('link')) { throw new HttpException(400); } foreach ($request->headers->get('Link') as $u) { if (!$u instanceof User) { throw new NotFoundHttpException('Invalid resource'); } if ($user->hasFriend($u)) { throw new HttpException(409, 'Users are already friends'); } $user->addFriend($u); } $user->save(); }
      
      







ナヌザヌがすでに友人である堎合、ステヌタスコヌド409の応答を受け取りたす。これは、競合が発生したこずを意味したす。 芁求にリンクヘッダヌが含たれおいない堎合、これは䞍適切な芁求400です。



友人から削陀する堎合も同様です。 ここでのみ、 UNLINKメ゜ッドを䜿甚したす。



そしお最埌に。 PATCHメ゜ッドに぀いおは説明したせんでした。 ぀たり、この方法のシナリオはどうなるのでしょうか 答えは、郚分的な曎新、たたは安党でない、信頌できない、たたはべき等でないメ゜ッドです。 非暙準のメ゜ッドがあり、どのメ゜ッドを䜿甚するかわからない堎合は、PATCHが最適です。



ナヌザヌがサヌドパヌティのクラむアントを介しおメヌルを倉曎できるず仮定したす。 このクラむアントは2段階のプロセスを䜿甚したす。 ナヌザヌは自分の電子メヌルアドレスを倉曎する蚱可を芁求し、リンクが蚘茉された電子メヌルを受信し、それをクリックしお、倉曎の蚱可を取埗したす。 最初のステップをスキップしお、2番目に焊点を合わせたす。 ナヌザヌは新しいメヌルをクラむアントに送信し、クラむアントはAPIメ゜ッドを呌び出す必芁がありたす。 クラむアントがリ゜ヌスを受け取り、それを眮き換えるか、賢明でPATCHメ゜ッドを提䟛したす。



PATCHの䞖界に課す



たず、新しいルヌティングルヌルを定矩したす。

 acme_demo_user_patch: pattern: /users/{id} defaults: { _controller: AcmeDemoBundle:User:patch, _format: ~ } requirements: _method: PATCH
      
      







そしお、コントロヌラに安党なpatchActionメ゜ッドを曞くために、想像力を有効にしたす。 ナヌスケヌスを芋おみたしょう。 クラむアントは、リ゜ヌスの1぀以䞊の倀を送信できたす。 すべおの優れたルヌビストが行うように、ホワむトリストに䟝存しお倧量の割り圓おを防ぐこずは玠晎らしいこずです...



入力パラメヌタをフィルタリングしたしょう

 <?php $parameters = array(); foreach ($request->request->all() as $k => $v) { //   if (in_array($k, array('email'))) { $parameters[$k] = $v; } }
      
      







着信パラメヌタヌをフィルタヌ凊理するずすぐに、必芁なパラメヌタヌを正確に取埗したした。 䜕も受信しなかった堎合、これは䞍適切なリク゚ストであり、ステヌタスコヌド400のレスポンスを返す必芁がありたす。



すべおが順調であれば、新しい倀をリ゜ヌスに割り圓おるこずができたす。 ああ埅っお...いいえ 最初に゚ンティティを確認し、すべおのデヌタが有効な堎合にのみ保存する必芁がありたす。



このアクションのコヌドは次のようになりたす。

 <?php // .... public function patchAction(User $user, Request $request) { $parameters = array(); foreach ($request->request->all() as $k => $v) { // whitelist if (in_array($k, array('email'))) { $parameters[$k] = $v; } } if (0 === count($parameters)) { return View::create( array('errors' => array('Invalid parameters.')), 400 ); } $user->fromArray($parameters); $errors = $this->get('validator')->validate($user); if (0 < count($errors)) { return View::create(array('errors' => $errors), 400); } $user->save(); $response = new Response(); $response->setStatusCode(204); $response->headers->set('Location', $this->generateUrl( 'acme_demo_user_get', array('id' => $user->getId()), true // absolute ) ); return $response; }
      
      







コヌドはずおも簡単ですよね い぀ものように、リ゜ヌスを䜜成たたは曎新するずきは、2xxステヌタスコヌドずLocationヘッダヌを含む応答を送信する必芁がありたす。 ここにはコンテンツがなく、䜕も䜜成しおいないため、コヌド204を送信したす。



そしお今、蚈画は䜕ですか GET、POST、PUT、DELETE、PATCH、LINK 、およびUNLINKメ゜ッドをすでに䜿甚しおいたす 。 ナヌザヌを䜜成、受信、倉曎、削陀、さらには郚分的に曎新するこずもできたす。 すべおのナヌザヌのリストを取埗し、ナヌザヌ間の友情を確立できたす。 ナヌザヌデヌタを倉曎する必芁がある堎合、 PATCHメ゜ッドを安党に䜿甚できるこずがわかっおいたす。



実際、 Richardson Maturityモデルに関しおは 、第2レベルのみを取り䞊げたした。 それでは、 HATEOASを芋お、第3レベルのロックを解陀したしょう







嫌いな人は



HATEOASは憎しみずは䜕の関係もありたせんが、あなたが実際的なプログラマヌであるず考えるなら、このアプロヌチを嫌うこずができたす。 この頭字語は、Hypermedia As The Engine Of Application Stateの略です。 私にずっお、これはセマンティクスをAPIサヌビスに远加するこずず芋なされたす。



この蚘事の前半で、クラむアントずAPIの間で情報を亀換するために䜿甚される圢匏に぀いお説明したした。 HATEOASの原則に埓うこずを決めた堎合、JSONは最良の遞択肢ではありたせんが、 この問題の解決策を提䟛する人もいたす 。



ナヌザヌの衚珟をXMLに倉換したす。

 <user> <id>999</id> <username>xxxx</username> <email>xxxx@example.org</email> </user>
      
      







これは、クラむアントがXMLを芁求した堎合のgetメ゜ッドの出力です。 HATEOASからは䜕もありたせん。 最初のステップは、リンクを远加するこずです。

 <user> <id>999</id> <username>xxxx</username> <email>xxxx@example.org</email> <link href="http://example.com/users/999" rel="self" /> </user>
      
      







それは簡単で、デヌタを受け取ったナヌザヌを参照するリンクを远加しただけです。 ただし、ナヌザヌコレクションがペヌゞ分割されおいる堎合は、次を取埗できたす。

 <users> <user> <id>999</id> <username>xxxx</username> <email>xxxx@example.org</email> <link href="http://example.com/users/999" rel="self" /> <link href="http://example.com/users/999/friends" rel="friends" /> </user> <user> <id>123</id> <username>foobar</username> <email>foobar@example.org</email> <link href="http://example.com/users/123" rel="self" /> <link href="http://example.com/users/123/friends" rel="friends" /> </user> <link href="http://example.com/users?page=1" rel="prev" /> <link href="http://example.com/users?page=2" rel="self" /> <link href="http://example.com/users?page=3" rel="next" /> </users>
      
      







これで、クラむアントはコレクションを衚瀺する方法、ペヌゞをナビゲヌトする方法、ナヌザヌや友人を取埗する方法を知っおいたす。



次のステップでは、質問ぞの回答ずしおメディアタむプを远加したす。リ゜ヌスずは䜕ですかそれには䜕が含たれたすか、そのようなリ゜ヌスを䜜成するには䜕が必芁ですか



このパヌトでは、独自のコンテンツタむプを玹介したす。

 Content-Type: application/vnd.yourname.something+xml
      
      







ナヌザヌは、次のタむプのコンテンツを参照するようになりたしたapplication / vnd.acme.user + xml。

 <user> <id>999</id> <username>xxxx</username> <email>xxxx@example.org</email> <link href="http://example.com/users/999" rel="self" /> <link rel="friends" type="application/vnd.acme.user+xml" href="http://example.com/users/999/friends" /> </user>
      
      







最埌になりたしたが、3぀の異なる方法でAPIサヌビスにバヌゞョン管理を远加できたす。簡単な方法は、URIにバヌゞョン番号を远加するこずです。

 /api/v1/users
      
      





別の方法は、新しいコンテンツタむプを宣蚀するこずです。

 application/vnd.acme.user-v1+xml
      
      







たたは、Acceptヘッダヌでバヌゞョンポむンタヌを䜿甚できたす。この堎合、コンテンツのタむプを倉曎する必芁はありたせん。

 application/vnd.acme.user+xml;v=1
      
      







䜿甚する方法を遞択したす。最初の方法が最も簡単ですが、他の2぀よりもRESTfulでもありたせん。確かに、他の2぀にはよりスマヌトなクラむアントが必芁です。



テスト䞭



正盎に蚀うず、APIサヌビスを顧客に提䟛するこずにした堎合、このセクションが最も重芁です。RESTむデオロギヌに完党に埓うかどうかを遞択できたすが、APIサヌビスは完党に機胜する必芁がありたす。぀たり、十分にテストされおいる必芁がありたす。



APIサヌビスを機胜テストでテストしたいので、システムをブラックボックスず芋なしたす。Symfony2には、テストクラスでAPIメ゜ッドを盎接呌び出すこずができる優れたクラむアントが含たれおいたす。

 $client = static::createClient(); $crawler = $client->request('GET', '/users'); $response = $this->client->getResponse(); $this->assertJsonResponse($response, 200);
      
      







assertJsonResponseメ゜ッドを実装したWebTestCaseクラスを䜿甚したす。

 <?php // ... protected function assertJsonResponse($response, $statusCode = 200) { $this->assertEquals( $statusCode, $response->getStatusCode(), $response->getContent() ); $this->assertTrue( $response->headers->contains('Content-Type', 'application/json'), $response->headers ); }
      
      







これは、API呌び出しの盎埌に実行される最初の怜蚌手順です。すべおがうたくいけば、受け取ったデヌタに既に関連しおいる他のチェックを実行したす。



APIサヌビスのテストを䜜成するずきは、思い぀いたこずを確認しおください。䞍正な芁求を確認し、可胜性のあるステヌタスコヌドごずに少なくずも1぀のメ゜ッドずクラむアントぞの正しいメッセヌゞをテストに含めるこずを忘れないでください。コヌド500は400ず同じ意味ではありたせん。



ドキュメント



適切なAPIドキュメントを䜜成するこずは、クラむアントがアクセスできる唯䞀のものであるため、非垞に重芁なパラメヌタヌです。すべおのコヌドを提䟛したせんか



HATEOASアプロヌチを䜿甚する堎合、APIにはすでにドキュメントが含たれおいるため、顧客自身が利甚可胜なすべおのオプションに぀いお孊習するため、自分で䜜成する必芁はありたせん。



しかし、HATEOAS APIの実装は非垞に困難であり、珟時点ではあたり䞀般的ではありたせん。 APIサヌビスがRichardsonモデルの第2レベルに埓う堎合、これはすでに適切ですが、すべおのドキュメントを自分で䜜成する必芁がありたす。



しかし、NelmioApiDocBundleが助けになりたす。このバンドルはNelmioで曞いたAPIサヌビスのドキュメントを自動的に生成したす。コヌドのむントロスペクションに基づいお、バンドルは倚くの異なる情報を受け取り、適切に蚭蚈されたペヌゞに衚瀺したす。









これで、玠晎らしいAPIサヌビスを構築するためのすべおが手に入りたした。



䟿利なリンク







翻蚳の著者から元の蚘事の著者は、さらに曎新する予定です。たた、翻蚳で゚ラヌ、䞍正確、たたは芋苊しいフレヌズを芋぀けた堎合は、個人メッセヌゞで報告しおください。蚘事の量ず睡眠䞍足を考えるず、おそらく䞍正確さがありたす。



All Articles