
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ã§ã¯ãã«ãŒãã£ã³ã°ã«ãŒã«ã®èª¬æã¯æ¬¡ã®ããã«ãªããŸãã
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 { public function allAction() { $users = UserQuery::create()->find(); return array('users' => $users); } 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圢åŒã®ã»ããã¢ããäŸïŒ
ããã©ã«ãã§ã¯ããªããžã§ã¯ãã®ãã¹ãŠã®ããããã£ãé€å€ããããšããå§ãããŸããå¿
èŠãªããããã£ã¯æç€ºçã«è¿œå ããå¿
èŠããããŸãã ããã«ããã倧ããªãªããžã§ã¯ãã®æè»æ§ãåäžããŸãã ããã¯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 { public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('username'); $builder->add('email', 'email'); $builder->add('password', 'password'); } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'Acme\DemoBundle\Model\User', 'csrf_protection' => false, )); } public function getName() { return 'user'; } }
ç§ã¯èªåã®æã§åŸ®èª¿æŽããå¿
èŠããããŸããïŒé»åã¡ãŒã«ãšãã¹ã¯ãŒãã®çš®é¡ããããŠCSRFä¿è·ããªãã«ããŸããã REST APIã§ã¯ãã»ãšãã©ã®å ŽåãOAuthãªã©ã®ã»ãã¥ãªãã£ã¬ã€ã€ãŒã䜿çšããŸãã RESTã³ã³ããã¹ãã§CSRFãä¿è·ããŠãæå³ããããŸããã
ããã§ãæ€èšŒã«ãŒã«ã远å ããå¿
èŠããããŸãã
é©åãªã³ã³ããŒãã³ãã®ãããã§
ãããã¯ç°¡åã«ãªããŸãã ãã¹ãŠã®çä¿¡ããŒã¿ãå®å
šãªæ¹æ³ã§ç°¡åã«ãã§ãã¯ã§ããã®ã§ããã®ã³ã³ããŒãã³ããæ¬åœã«æ°ã«å
¥ã£ãŠããŸãã
ç§ãã¡ã®ã±ãŒã¹ã«æ»ã£ãŠãYAMLã§æ€èšŒã«ãŒã«ã説æããã®ã«æ
£ããŠããŸããã誰ãããªãã®éžæãå¶éããŸããã 以äžã«äŸã瀺ããŸãã
ã³ã³ãããŒã©ãŒã§ã¡ãœãããæžããŸãããïŒ
<?php
ããäžã€ã®ãã³ãã ãã©ãŒã ã®åŠçã«ã¯ãåžžã«å¥ã®æ¹æ³ã䜿çšããŠãã ããã ããããããªãã¯èªåã«æè¬ããŸãã
processFormïŒïŒã¡ãœããã¯æ¬¡ã®ããã«ãªããŸãã
èŠããã«ããã©ãŒã ãäœæããåä¿¡ããŒã¿ãããã«ãã€ã³ããããã¹ãŠã®ããŒã¿ãæå¹ã§ããã°ããŠãŒã¶ãŒãä¿åããŠå¿çãè¿ããŸãã åé¡ãçºçããå Žåã¯ããã©ãŒã ãšãšãã«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:
ã¯ã©ã€ã¢ã³ããšããŠ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
ãªãœãŒã¹ã倿Žãããšããããšã¯ããã®ãªãœãŒã¹ã«é¢ãããã¹ãŠããã§ã«ç¥ã£ãŠããããã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
æ°åè¡ã®ã³ãŒããæžããã ãã§ãCRUDæäœãå®å
šã«å®è£
ããå®å
šã«æ©èœããAPIãå®è£
ããŸããã ã§ã¯ããŠãŒã¶ãŒã€ã³ã¿ã©ã¯ã·ã§ã³ã®è¿œå ã«ã€ããŠã¯ã©ãã§ããããïŒ äŸãã°ãåæ
ïŒ
RESTãä»ããŠç¹å®ã®ãŠãŒã¶ãŒã®ãã¬ã³ããªã¹ããååŸããæ¹æ³ å人ãç¹å®ã®ãŠãŒã¶ãŒã«å±ãããŠãŒã¶ãŒã®ã³ã¬ã¯ã·ã§ã³ãšèããå¿
èŠããããŸãã ãã®çžäºäœçšãå®è£
ããŸãããã
åæ
ã¢ã«ãŽãªãºã
æåã«ãæ°ããã«ãŒãã£ã³ã°ã«ãŒã«ãäœæããå¿
èŠããããŸãã ç§ãã¡ã¯åéããŠãŒã¶ãŒã®ã³ã¬ã¯ã·ã§ã³ãšèããŠããã®ã§ããªãœãŒã¹ããçŽæ¥ååŸããŸãã
acme_demo_user_get_friends: pattern: /users/{id}/friends defaults: { _controller: AcmeDemoBundle:User:getFriends, _format: ~ } requirements: _method: GET
ãã®ã¢ã¯ã·ã§ã³ã¯ãã³ã³ãããŒã©ãŒã§ã¯æ¬¡ã®ããã«ãªããŸãã
<?php
以äžã§ãã æ¬¡ã«ãå¥ã®ãŠãŒã¶ãŒã®åéã«ãªãããã»ã¹ã説æããæ¹æ³ã«ã€ããŠèããŠã¿ãŸãããã
ãããRESTã§ã©ã®ããã«ç®¡çããŸããïŒ äœãäœæããªããããåéã®ã³ã¬ã¯ã·ã§ã³ã«POSTã䜿çšããããšã¯ã§ããŸããã äž¡æ¹ã®ãŠãŒã¶ãŒããã§ã«ååšããŸãã ã³ã¬ã¯ã·ã§ã³å
šäœãæ¬åœã«çœ®ãæããããªããããPUTã¡ãœããã䜿çšããããšã¯ã§ããŸããã ããã¯æ¬åœã«ç§ãã¡ãå°æãããããšãã§ããŸã...
ãã ããHTTPãããã³ã«ä»æ§ã«ã¯ãåé¡ã解決ããLINKã¡ãœãããèšè¿°ãããŠããŸãã ããã¯èšããŸãïŒ
LINKã¡ãœããã¯ãRequest-URIã§æå®ãããæ¢åã®ãªãœãŒã¹ãšä»ã®æ¢åã®ãªãœãŒã¹ãšã®éã«1ã€ä»¥äžã®é¢ä¿ã確ç«ããŸãã
ããããŸãã«ç§ãã¡ãå¿
èŠãšãããã®ã§ãã 2ã€ã®ãªãœãŒã¹ããªã³ã¯ãããã®ã§ãAPIãµãŒãã¹ãäœæãããšãã«ãªãœãŒã¹ãå¿ããŠã¯ãªããŸããã ã§ã¯ãsymfony2ã§ãããè¡ãæ¹æ³ã¯ïŒ
ç§ã®æ¹æ³ã¯ãèŠæ±ãªã¹ããŒã䜿çšããããšã«åºã¥ããŠããŸãã ã¯ã©ã€ã¢ã³ãã¯ããªãœãŒã¹ã®LINKèŠæ±ãéä¿¡ããå°ãªããšã1ã€ã®
ãªã³ã¯ããããŒãéä¿¡ããŸãã
LINK /users/1 Link: <http:
ã¯ãšãªãªã¹ãã䜿çšãããšãã¡ãœããã§ã¯ãªãŒã³ãªå
¥åãååŸã§ãããããéåžžã«äŸ¿å©ãªãªãã·ã§ã³ã§ãã æçµçã«ããã®ã¡ãœããã®ç®çã¯ãªããžã§ã¯ãããªã³ã¯ããããšã§ãããã³ã³ãããŒã©ãŒã§URIãæäœããããããŸããã ãã®åã«ãã¹ãŠã®å€æãå®è¡ããå¿
èŠããããŸãã
ãªã¯ãšã¹ããªã¹ããŒã¯ããããã¹ãŠã®
ãªã³ã¯ããããŒãååŸããããããSymfony2
RouterMatcherã³ã³ããŒãã³ãã«äœ¿çšããŠã³ã³ãããŒã©ãŒãšã¡ãœããã®ååãååŸããŸãã ãŸãããã©ã¡ãŒã¿ãæºåããŸãã
ã€ãŸããã³ã³ãããŒã©ãŒãäœæããå¿
èŠãªãã©ã¡ãŒã¿ãŒã䜿çšããŠé©åãªã¡ãœãããåŒã³åºãããã«å¿
èŠãªãã¹ãŠã®æ
å ±ãå«ãŸããŠããŸãã ãã®äŸã§ã¯ãUserControllerã®getUserïŒïŒã¡ãœãããå
LinkããããŒã«å¯ŸããŠåŒã³åºãããŸãã ãããããã©ã¡ãŒã¿ãŒã³ã³ããŒã¿ãŒã䜿çšããªãã£ãçç±ã§ããããã«ãããåŒæ°ã®å€ãšããŠidã䜿çšã§ããããã«ãªãããã®ãããªãœãŒã¹ãååŸã§ããŸããã ç§ã¯ããã€ãã®ä»®å®ãããŸããïŒ
- ãŠãŒã¶ãŒãååšããªãå ŽåãäŸå€ãè¿ãããŸãã
- View泚éã䜿çšãããããæ»ãå€ãšããŠé
åãååŸããŸãã
ãªãœãŒã¹ãªããžã§ã¯ããååŸããããããããèŠæ±å±æ§ãšããŠé
眮ãããªã¹ããŒã®å Žåãäœæ¥ã¯å®äºããŸãã ã³ãŒãã¯æ¬¡ã®ãšããã§ãã
<?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 { private $resolver; private $urlMatcher; 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'); while (preg_match('/^((?:[^"]|"[^"]*")*?),/', $header, $matches)) { $header = trim(substr($header, strlen($matches[0]))); $links[] = $matches[1]; } if ($header) { $links[] = $header; } $requestMethod = $this->urlMatcher->getContext()->getMethod();
ããã§ãã«ãŒãã£ã³ã°ã«ãŒã«ãäœæã§ããŸãã
acme_demo_user_link: pattern: /users/{id} defaults: { _controller: AcmeDemoBundle:User:link, _format: ~ } requirements: _method: LINK
ãããŠãã¢ã¯ã·ã§ã³ã®ã³ãŒãã¯æ¬¡ã®ããã«ãªããŸãã
<?php
ãŠãŒã¶ãŒããã§ã«å人ã§ããå Žåãã¹ããŒã¿ã¹ã³ãŒã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) {
çä¿¡ãã©ã¡ãŒã¿ãŒããã£ã«ã¿ãŒåŠçãããšããã«ãå¿
èŠãªãã©ã¡ãŒã¿ãŒãæ£ç¢ºã«ååŸããŸããã äœãåä¿¡ããªãã£ãå Žåãããã¯äžé©åãªãªã¯ãšã¹ãã§ãããã¹ããŒã¿ã¹ã³ãŒã400ã®ã¬ã¹ãã³ã¹ãè¿ãå¿
èŠããããŸãã
ãã¹ãŠãé 調ã§ããã°ãæ°ããå€ããªãœãŒã¹ã«å²ãåœãŠãããšãã§ããŸãã ããåŸ
ã£ãŠ...ãããïŒ æåã«ãšã³ãã£ãã£ã確èªãããã¹ãŠã®ããŒã¿ãæå¹ãªå Žåã«ã®ã¿ä¿åããå¿
èŠããããŸãã
ãã®ã¢ã¯ã·ã§ã³ã®ã³ãŒãã¯æ¬¡ã®ããã«ãªããŸãã
<?php
ã³ãŒãã¯ãšãŠãç°¡åã§ãããïŒ ãã€ãã®ããã«ããªãœãŒã¹ãäœæãŸãã¯æŽæ°ãããšãã¯ã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
ããã¯ãAPIåŒã³åºãã®çŽåŸã«å®è¡ãããæåã®æ€èšŒæé ã§ãããã¹ãŠãããŸãããã°ãåãåã£ãããŒã¿ã«æ¢ã«é¢é£ããŠããä»ã®ãã§ãã¯ãå®è¡ããŸããAPIãµãŒãã¹ã®ãã¹ããäœæãããšãã¯ãæãã€ããããšã確èªããŠãã ãããäžæ£ãªèŠæ±ã確èªããå¯èœæ§ã®ããã¹ããŒã¿ã¹ã³ãŒãããšã«å°ãªããšã1ã€ã®ã¡ãœãããšã¯ã©ã€ã¢ã³ããžã®æ£ããã¡ãã»ãŒãžããã¹ãã«å«ããããšãå¿ããªãã§ãã ãããã³ãŒã500ã¯400ãšåãæå³ã§ã¯ãããŸãããããã¥ã¡ã³ã
é©åãªAPIããã¥ã¡ã³ããäœæããããšã¯ãã¯ã©ã€ã¢ã³ããã¢ã¯ã»ã¹ã§ããå¯äžã®ãã®ã§ãããããéåžžã«éèŠãªãã©ã¡ãŒã¿ãŒã§ãããã¹ãŠã®ã³ãŒããæäŸããŸãããïŒHATEOASã¢ãããŒãã䜿çšããå ŽåãAPIã«ã¯ãã§ã«ããã¥ã¡ã³ããå«ãŸããŠããããã顧客èªèº«ãå©çšå¯èœãªãã¹ãŠã®ãªãã·ã§ã³ã«ã€ããŠåŠç¿ãããããèªåã§äœæããå¿
èŠã¯ãããŸãããããããHATEOAS APIã®å®è£
ã¯éåžžã«å°é£ã§ãããçŸæç¹ã§ã¯ããŸãäžè¬çã§ã¯ãããŸããã APIãµãŒãã¹ãRichardsonã¢ãã«ã®ç¬¬2ã¬ãã«ã«åŸãå Žåãããã¯ãã§ã«é©åã§ããããã¹ãŠã®ããã¥ã¡ã³ããèªåã§äœæããå¿
èŠããããŸããããããNelmioApiDocBundleãå©ãã«ãªããŸãããã®ãã³ãã«ã¯Nelmioã§æžããAPIãµãŒãã¹ã®ããã¥ã¡ã³ããèªåçã«çæããŸããã³ãŒãã®ã€ã³ããã¹ãã¯ã·ã§ã³ã«åºã¥ããŠããã³ãã«ã¯å€ãã®ç°ãªãæ
å ±ãåãåããé©åã«èšèšãããããŒãžã«è¡šç€ºããŸãã
ããã§ãçŽ æŽãããAPIãµãŒãã¹ãæ§ç¯ããããã®ãã¹ãŠãæã«å
¥ããŸããã䟿å©ãªãªã³ã¯
翻蚳ã®èè
ããïŒå
ã®èšäºã®èè
ã¯ãããã«æŽæ°ããäºå®ã§ãããŸãã翻蚳ã§ãšã©ãŒãäžæ£ç¢ºããŸãã¯èŠèŠãããã¬ãŒãºãèŠã€ããå Žåã¯ãå人ã¡ãã»ãŒãžã§å ±åããŠãã ãããèšäºã®éãšç¡ç äžè¶³ãèãããšãããããäžæ£ç¢ºãããããŸãã