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

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.

Sarah KHALIL

October 21, 2015
Tweet

More Decks by Sarah KHALIL

Other Decks in Technology

Transcript

  1. Who I am? • Head of • Trainer & Developer

    • Enjoying sharer • Contributor to
  2. What is REST? • REpresentational State Transfer • Architecture (not

    a protocol!) • Associated to Service Oriented Architecture • Management of resources
  3. 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.
  4. Code on demand (6/6) • The client should be executing

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

    • DELETE • OPTIONS • CONNECT • HEAD
  6. Keep’em in mind • JSON, XML, HTML… • By developers

    and for developers • Scalability • Latency • Security !
  7. 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>
  8. 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’) ); } }
  9. Exclusion policy /** * @JMS\ExclusionPolicy("all") */ class Product { /**

    * @JMS\Expose() */ private $name; private $users; } • Visitor pattern • Object graph • Relations (xToMany)
  10. 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', ], ]; }
  11. 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; }
  12. 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', ), ); } }
  13. Controller # app/config/config.yml fos_rest: view: view_response_listener: force sensio_framework_extra: view: {

    annotations: false } /** * @View() */ public function getUsersAction() { … return $data; }
  14. 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…).
  15. 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
  16. 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) { }
  17. Validation (with the Symfony Validator component) Configuration fos_rest: body_converter: enabled:

    true validate: true validation_errors_argument: validationErrors
  18. 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 } }
  19. /** * @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
  20. 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
  21. 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
  22. HAL • Hypertext Application Language • _links - relations •

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

    info such as pagination… • links - Relations (xToOne - xToMany) • linked - Resource(s) linked • Specifications : http://jsonapi.org/format/
  24. /** * @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
  25. /** * … * @Hateoas\Relation( * "activity", * href =

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

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

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

    with the properties you need. • Let the serializer do its job.
  30. Guzzle Bundle • HTTP Client • Request • Response •

    HTTPS • Error management • https://github.com/misd- service-development/guzzle- bundle • https://github.com/csarrazi/ CsaGuzzleBundle
  31. 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!