ZendCon 2015 - Build Restful API easily with Symfony

ZendCon 2015 - Build Restful API 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" Restfull API.
Then we'll see some code with the usage of :
FOSRestBundle, and save a lot of time;
JMS serializer and its awesome features;
the HATEOAS project, very nice to unlock the third level of the Richardson maturity model;
And finaly, 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.

34ade09dd3d11004ca8ee4174fd3d6a2?s=128

Sarah KHALIL

October 21, 2015
Tweet

Transcript

  1. Build Restful API easily with Symfony Sarah Khalil - October

    21st, 2015
  2. Who I am? • Head of • Trainer & Developer

    • Enjoying sharer • Contributor to
  3. None
  4. What is REST?

  5. What is REST? • REpresentational State Transfer • Architecture (not

    a protocol!) • Associated to Service Oriented Architecture • Management of resources
  6. The 6 constraints of REST

  7. Client - Server (1/6) • Separation of concerns. • Portability.

  8. Stateless (2/6) • Based on HTTP. • Session has to

    be held on the client side.
  9. Cacheable (3/6) • Avoid useless requests.

  10. Layered system (4/6) • The client requests a resource but

    it doesn’t know the system behind.
  11. Uniform interface (5/6) • Each resource has its identified: the

    URI. • Each resource has its representation. • Auto-descripted message: if it’s JSON, the HTTP response has a content-type header saying.
  12. Code on demand (6/6) • The client should be executing

    some code coming from the server, to avoid more work on the server side.
  13. HTTP Methods • GET • POST • PUT • PATCH

    • DELETE • OPTIONS • CONNECT • HEAD
  14. Restful API

  15. Keep’em in mind • JSON, XML, HTML… • By developers

    and for developers • Scalability • Latency • Security !
  16. http://martinfowler.com

  17. HATEOAS • Hypermedia as the engine of Application State •

    Discoverability
  18. None
  19. Symfony Full Stack JMSSerializerBundle FOSRestBundle BazingaHateoasBundle NelmioApiDocBundle

  20. Symfony Full Stack JMSSerializerBundle FOSRestBundle BazingaHateoasBundle NelmioApiDocBundle

  21. Symfony Full Stack JMSSerializerBundle FOSRestBundle BazingaHateoasBundle NelmioApiDocBundle

  22. Philosophy

  23. Request GET /users HTTP/1.1 User-Agent: curl/7.37.1 Host: 127.0.0.1:8000 Accept: */*

  24. Symfony\Component\HttpFoundation\Request

  25. Response 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>
  26. Symfony\Component\HttpFoundation \Response

  27. HttpKernel

  28. Symfony Full Stack JMSSerializerBundle FOSRestBundle BazingaHateoasBundle NelmioApiDocBundle

  29. Symfony Full Stack JMSSerializerBundle FOSRestBundle BazingaHateoasBundle NelmioApiDocBundle Work with resources

  30. None
  31. Json/XML Object serialization deserialization

  32. Configuration • XML, YML and Annotation. • http://jmsyst.com/libs/serializer/master/reference • Helper

    to serialize properties the way you need.
  33. 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’) ); } }
  34. 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 } Output
  35. Exclusion policy /** * @JMS\ExclusionPolicy("all") */ class Product { /**

    * @JMS\Expose() */ private $name; private $users; } • Visitor pattern • Object graph • Relations (xToMany)
  36. Serializer handler • Specific needs • implements JMS\Serializer\Handler\SubscribingHandlerInterface • <tag

    name="jms_serializer.subscribing_handler" />
  37. 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', ], ]; }
  38. 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; }
  39. Event subscriber • http://jmsyst.com/libs/serializer/master/event_system • implements JMS\Serializer\EventDispatcher\EventSubscriberInterface <tag name="jms_serializer.event_subscriber" type="AppBundle\MyClass"

    direction="serialization" format="json" method="onPostSerialize" />
  40. serialization deserialization

  41. Object serialization deserialization

  42. Object serialization deserialization serializer.pre_serialize

  43. Object serialization deserialization serializer.pre_serialize

  44. Object serialization deserialization serializer.pre_serialize serializer.post_serialize

  45. Json/XML Object serialization deserialization serializer.pre_serialize serializer.post_serialize

  46. Json/XML Object serialization deserialization serializer.pre_serialize serializer.post_serialize serializer.pre_deserialize

  47. Json/XML Object serialization deserialization serializer.pre_serialize serializer.post_serialize serializer.pre_deserialize

  48. Json/XML Object serialization deserialization serializer.pre_serialize serializer.post_serialize serializer.pre_deserialize serializer.post_deserialize

  49. 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', ), ); } }
  50. Other features • Serialization group • Versionning

  51. Symfony Full Stack JMSSerializerBundle FOSRestBundle BazingaHateoasBundle NelmioApiDocBundle

  52. Symfony Full Stack JMSSerializerBundle FOSRestBundle BazingaHateoasBundle NelmioApiDocBundle Setup your Symfony

    application
  53. None
  54. Controller # app/config/config.yml fos_rest: view: view_response_listener: force sensio_framework_extra: view: {

    annotations: false } /** * @View() */ public function getUsersAction() { … return $data; }
  55. Content-Type negociation • 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…).
  56. Content-Type negociation • Priorities define the order of media types

    as the application prefers: • Request: Accept text/json;q=0.9,*/*;q=0.8,text/html # app/config/config.yml fos_rest: format_listener: rules: - { path: '^/', priorities: ['json'], fallback_format: html } weighting
  57. 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 updateAction(Post $post) { }
  58. Validation

  59. Validation (with the Symfony Validator component) Configuration fos_rest: body_converter: enabled:

    true validate: true validation_errors_argument: validationErrors
  60. Validation (with the Symfony Validator component) Usage /** * @ParamConverter("post",

    converter="fos_rest.request_body") */ public function putPostAction(Post $post, ConstraintViolationListInterface $validationErrors) { if (count($validationErrors) > 0) { // Handle validation errors } }
  61. Param Fetcher

  62. /** * @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
  63. /** * @QueryParam(name="page", requirements="\d+", default="1") */ public function getArticlesAction($page) {}

    fos_rest.param_fetcher_listener: force
  64. Error handling

  65. Error handling (1/2) fos_rest: codes: 'Symfony\Component\Routing\Exception\ResourceNotFoundException': 404 'Doctrine\ORM\OptimisticLockException': HTTP_CONFLICT

  66. Error handling (2/2) fos_rest: view: exception_wrapper_handler: My\Bundle\Handler\MyExceptionWrapperHandler implements FOS\RestBundle\ViewExceptionWrapperHandlerInterface public

    function wrap($data) { return new MyExceptionWrapper($data); } Take exemple on FOS\RestBundle\Util\ExceptionWrapper
  67. 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
  68. Symfony Full Stack JMSSerializerBundle FOSRestBundle BazingaHateoasBundle NelmioApiDocBundle

  69. Symfony Full Stack JMSSerializerBundle FOSRestBundle BazingaHateoasBundle NelmioApiDocBundle Unlock the level

    3 of the Richardson’s model
  70. None
  71. HAL • Hypertext Application Language • _links - relations •

    _embedded - related resource(s) • self • Specifications: http://stateless.co/hal_specification.html
  72. JSON API • Similar to HAL • meta - meta

    info such as pagination… • links - Relations (xToOne - xToMany) • linked - Resource(s) linked • Specifications : http://jsonapi.org/format/
  73. Configuration • Annotation, XML and YML • Relies on Expression

    Language (since Symfony 2.4)
  74. /** * @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
  75. Result { "id": 1, "_links": { "self": { "href": "http://domaine.name/users/eh1754329986/notifications/1/"

    } } }
  76. /** * … * @Hateoas\Relation( * "activity", * href =

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

    "content": "Activity content", "_links": { "self": { "href": "/activities/3" } } } } }
  78. Representation • Use representation to decorate your resource • Hateoas\Representation\PaginatedRepresentation

    • And many more: https://github.com/willdurand/Hateoas/tree/master/src/Hateoas/Representation
  79. "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" } }
  80. Representation • Any representation you need ! • A class

    with the properties you need. • Let the serializer do its job.
  81. Symfony Full Stack JMSSerializerBundle FOSRestBundle BazingaHateoasBundle NelmioApiDocBundle Guzzle

  82. Symfony Full Stack JMSSerializerBundle FOSRestBundle BazingaHateoasBundle NelmioApiDocBundle Guzzle Communicate between

    micro services
  83. Frontend Service 1 Service 2 Service 3 Service 4 Private

    network Client
  84. Guzzle Bundle • HTTP Client • Request • Response •

    HTTPS • Error management • https://github.com/misd- service-development/guzzle- bundle • https://github.com/csarrazi/ CsaGuzzleBundle
  85. None
  86. None
  87. Security

  88. Symfony Full Stack JMSSerializerBundle FOSRestBundle BazingaHateoasBundle NelmioApiDocBundle

  89. 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!
  90. Authorization • access_control • $this->get(‘security.authorization_checker’)->isGranted('ROLE_ADMIN') • {% if is_granted(‘ROLE_ADMIN’) %}

    • Voter • ACL • …
  91. FosHttpCacheBundle Keep it mind

  92. None
  93. One last thing

  94. Symfony Full Stack JMSSerializerBundle FOSRestBundle BazingaHateoasBundle NelmioApiDocBundle

  95. Symfony Full Stack JMSSerializerBundle FOSRestBundle BazingaHateoasBundle NelmioApiDocBundle Documentation

  96. None
  97. None
  98. Oh I forgot to introduce you Lannister! @catlannister

  99. http://tinyurl.com/sfcon2015

  100. http://tinyurl.com/sfliveusa

  101. Thank you! @saro0h speakerdeck.com/saro0h/ saro0h This is a zero guys!