Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHP UK 2015 - Build RESTful APIs easily with Symfony

Sarah KHALIL
February 17, 2015

PHP UK 2015 - Build RESTful APIs easily with Symfony

Let's build a nice REST API with all the recent helper we can find. First of all, we will take some time to see, in theory, what's asked to build a "good" Restful API. Then we'll see some code with the usage of :
FOSRestBundle to save time, a lot;
JMSSerializerBundle and its awesome features;
The HATEOAS project, very nice to unlock the third level of the Richardson maturity model;
And finally, a little bit of Guzzle to ease communication between applications (going further with the SOA architecture).

The goal of that talk is to demystify all technologies in the "REST" galaxy.

Sarah KHALIL

February 17, 2015
Tweet

More Decks by Sarah KHALIL

Other Decks in Programming

Transcript

  1. What is REST? ❖ REpresentational State Transfer ❖ Architecture ❖

    Associated to Service Oriented Architecture ❖ Management of resources
  2. Uniform interface (5/6) ❖ Each resource has its identifier: URI.

    ❖ Each resource has its representation. ❖ Auto-descripted message: if it’s JSON, the HTTP Response has a content-type header saying so.
  3. Code on demand (optional) (6/6) ❖ The client could execute

    some code coming from the server, to avoid more work on the server side.
  4. HTTP/1.1 200 OK Host: 127.0.0.1:8000 Connection: close X-Powered-By: PHP/5.6.3 Set-Cookie:

    PHPSESSID=ecdadm6o2d5v9ei0m181j8gh96; path=/ Cache-Control: no-cacheDate: Wed, 11 Feb 2015 23:42:50 GMT Content-Type: text/html; charset=UTF-8 X-Debug-Token: 32860d X-Debug-Token-Link: /_profiler/32860d <!DOCTYPE html> <html lang="en"> <head>
  5. Usage class DefaultController { public function indexAction() { $obj =

    new \StdClass(); $obj->latitude = -33.87087; $obj->longitude = 151.225459; $data = $this->get(‘jms_serializer’) ->serialize($obj, ’json’); return new Response( $data, 200, array(‘Content-Type’=>’application/json’) ); } } HTTP/1.1 200 OK Host: 127.0.0.1 X-Powered-By: PHP/5.6.3 Content-Type: application/json { "latitude": -33.87087, "longitude": 151.225459 }
  6. Exclusion strategy /** * @JMS\ExclusionPolicy("all") */ class Product { /**

    * @JMS\Expose() */ private $name; private $users; } ❖ Visitor pattern ❖ Object graph ❖ Relations (xToMany)
  7. public static function getSubscribingMethods() { return [ [ 'direction' =>

    GraphNavigator::DIRECTION_SERIALIZATION, 'format' => ‘json', 'type' => ‘AppBundle\MyClass’, 'method' => ‘serialize', ], [ 'direction' => GraphNavigator::DIRECTION_DESERIALIZATION, 'format' => ‘json', 'type' => ‘AppBundle\MyClass', 'method' => ‘deserialize', ], ]; }
  8. public function serialize( JsonSerializationVisitor $visitor, MyClass $object, array $type, Context

    $context ) { $data = $object->toArray(); return $data; } public function deserialize(JsonDeserializationVisitor $visitor, $data) { $object = new AppBundle\MyClass($data); return $object; }
  9. Event Subscriber ❖ http://jmsyst.com/libs/serializer/master/event_system ❖ serializer.pre_serialize, serializer.post_serialize, serializer.pre_deserialize, serializer.post_deserialize ❖

    implements JMS\Serializer\EventDispatcher\EventSubscriberInterface ❖ <tag name="jms_serializer.event_subscriber" type="AppBundle\MyClass" direction="serialization" format="json" method="onPostSerialize"/>
  10. class PostSerializeMediaListener implements EventSubscriberInterface { public function onPostSerialize(ObjectEvent $event) {

    $myObject = $event->getObject(); $event->getVisitor()->addData('newElementInJson', 'information’); } public static function getSubscribedEvents() { return array( array( 'event' => Events::POST_SERIALIZE, 'format' => ‘json', 'class' => ‘AppBundle\MyClass', 'method' => ‘onPostSerialize', ), ); } }
  11. Controller ❖ Return view / data # app/config/config.yml fos_rest: view:

    view_response_listener: force sensio_framework_extra: view: { annotations: false } /** * @View() */ public function getUsersAction() { … return $data; }
  12. Content type negotiation ❖ Request = > Accept ❖ priorities

    of media type(s) that your application is ready to get ❖ Response => Content-Type ❖ the media type of the response (html, json, png…)
  13. Content type negotiation ❖ Priorities define the order of media

    types as the application prefers # app/config/config.yml fos_rest: format_listener: rules: - { path: '^/', priorities: ['json'], fallback_format: html } ❖ Request: ❖ Accept text/json;q=0.9,*/*;q=0.8,text/html
  14. Request Body Converter ❖ Decoder ❖ ParamConverter # app/config/config.yml sensio_framework_extra:

    request: { converters: true } fos_rest: body_converter: enabled: true /** * @ParamConverter("post", converter="fos_rest.request_body") */ public function putPostAction(Post $post) { }
  15. Validation (with the Symfony Validator Component) fos_rest: body_converter: enabled: true

    validate: true validation_errors_argument: validationErrors /** * @ParamConverter("post", converter="fos_rest.request_body") */ public function putPostAction(Post $post, ConstraintViolationListInterface $validationErrors) { if (count($validationErrors) > 0) { // Handle validation errors } }
  16. /** * @QueryParam( * name="sort", * requirements="(asc|desc)", * allowBlank=false, *

    default="asc", * description="Sort direction" * ) * @RequestParam( * name="firstname", * requirements="[a-z]+" * ) * @QueryParam( * array=true, * name="filters", * requirements=@MyComplexConstraint * ) */ public function getArticlesAction(ParamFetcher $paramFetcher) {} http://mydomain.com/resources?sort=desc In $request->request ($_POST) fos_rest.param_fetcher_listener: true
  17. Error handling fos_rest: view: exception_wrapper_handler: My\Bundle\Handler\MyExceptionWrapperHandler ❖ implements FOS\RestBundle\ViewExceptionWrapperHandlerInterface public

    function wrap($data) { return new MyExceptionWrapper($data); } ❖ Take example on FOS\RestBundle\Util\ExceptionWrapper
  18. Other Features ❖ Automatic generation of routes ❖ Consistency ❖

    Versioning ❖ url ❖ mime type ❖ Take a look at the documentation : ❖ http://symfony.com/doc/master/bundles/FOSRestBundle/index.html
  19. HAL ❖ Hypertext Application Language ❖ _links - relations ❖

    _embedded - related resource(s) ❖ self ❖ Specifications: http://stateless.co/hal_specification.html
  20. JSON Api ❖ Similar to HAL ❖ meta - meta

    info such as pagination… ❖ links - Relation (xToOne, xToMany…) ❖ linked - Resource(s) linked ❖ Specifications: http://jsonapi.org/format/
  21. /** * @Hateoas\Relation( * "self", * href = @Hateoas\Route( *

    "get_notification", * absolute= true, * parameters = { * "id" = "expr(object.getId())", * "user" = "expr(service(‘security.context’).getToken().getUser().getUuid())" * } * ) * ) */ class Notification
  22. /** * … * @Hateoas\Relation( * "activity", * href =

    "expr(‘/activity/'~object.getActivity().getId()", * embedded = "expr(object.getActivity())", * exclusion = @Hateoas\Exclusion(excludeIf = "expr(object.getActivity() === null)") * ) */ class Notification
  23. Result { "id": 1, "_embedded": { "activity": { "id": 3,

    "content": "Activity content", "_links": { "self": { "href": "/activities/3" } } } } }
  24. "page": 1, "limit": 10, "pages": "100", "total": 1000, "_links": {

    "self": { "href": "http://domain.name/notifcations?page=1&limit=10" }, "first": { "href": "http://domain.name/notifcations?page=1&limit=1" }, "last": { "href": "http://domain.name/notifcations?page=100&limit=1" }, "next": { "href": "http://domain.name/notifcations?page=2&limit=1" } }
  25. Representation ❖ Any representation you want ! ❖ A class

    with the properties you need ❖ Let the serializer do its job
  26. Guzzle Bundle ❖ HTTP client ❖ Request ❖ Response ❖

    HTTPS ❖ Error management ❖ https://github.com/misd-service- development/guzzle-bundle ❖ https://github.com/csarrazi/ CsaGuzzleBundle
  27. Authentication ❖ Authorization header : Authorization: Bearer <access_token> ❖ https://github.com/poledev/katas/tree/kata-authentication

    ❖ http://symfony.com/doc/current/cookbook/security/ api_key_authentication.html ❖ Make it stateless
  28. Some references ❖ Symfony Rest Edition: https://github.com/gimler/symfony-rest-edition ❖ Lukas Kahwe

    Smith & William Durand - Build Awesome REST APIs With Symfony2: https://www.youtube.com/watch?v=AcLHvOT5Ekg ❖ REST+JSON API Design - Best Practices for Developers: https://www.youtube.com/watch? v=hdSrT4yjS1g ❖ http://json-ld.org/