$30 off During Our Annual Pro Sale. View Details »

REST dans le monde Symfony (Bdx.io)

REST dans le monde Symfony (Bdx.io)

Après vous avoir convaincu que vous n'avez probablement jamais créé d'APIs "REST" de votre vie, je vous propose un tour d'horizon des outils disponibles pour concevoir de vraies APIs REST en PHP (et oui !) grâce à l'écosystème du framework Symfony.
Cette présentation, mêlant théorie et pragmatisme, fera l'état des lieux de "REST" dans le monde Symfony en passant différentes couches en revue telles le routing, la sérialisation, la sécurité mais également la documentation de vos APIs.

Online slides: http://friendsofsymfony.github.io/slides/rest-dans-le-monde-symfony.html
Sources: https://github.com/FriendsOfSymfony/friendsofsymfony.github.com

William Durand

October 17, 2014
Tweet

More Decks by William Durand

Other Decks in Programming

Transcript

  1. REST dans le
    monde Symfony
    William Durand - October 17th, 2014

    View Slide

  2. Introduction to REST

    View Slide

  3. REpresentational State Transfer
    REST is the underlying architectural principle of the web.
    It is formalized as a set of constraints, described in
    Roy Fielding's dissertation.

    View Slide

  4. Richardson Maturity Model
    http://martinfowler.com/articles/richardsonMaturityModel.html

    View Slide

  5. Level 0 - The Swamp of POX
    HTTP as a tunneling mechanism
    RPC style system (SOAP, XML-RPC)

    View Slide

  6. Level 1 - Resources
    Individual resources (URIs)
    Notion of object identity

    View Slide

  7. Level 2 - HTTP Verbs
    Client
    uses specific HTTP
    verbs
    Server
    uses HTTP
    status codes

    View Slide

  8. HTTP Verbs
    Method Safe? Idempotent?
    GET yes yes
    HEAD yes yes
    POST no no
    PUT no yes
    DELETE no yes (*)
    ... no no
    Safe: means cacheable.
    Idempotent: result independent
    on the number of executions.

    View Slide

  9. HTTP Status Codes
    Code range Description Example
    1xx Information 100 - Continue
    2xx Successful 201 - Created
    3xx Redirection 301 - Moved Permanently
    4xx Client Error 404 - Not Found
    5xx Server Error 501 - Not Implemented

    View Slide

  10. Level 3 - Hypermedia Controls
    Service discovery via link relations
    Hypermedia formats (ATOM, HAL, JSON-LD, etc.)

    View Slide

  11. Level 3
    =
    Content Type Negotiation
    +
    HATEOAS

    View Slide

  12. Content Type Negotiation
    Content Type Negotiation is the principle of finding appropriate
    response formats based on client requirements.
    No standardized algorithm available
    Apache algorithm is documented though
    Also covers encoding (Accept-Encoding) and language
    (Accept-Language) negotiation
    mod_negotiation

    View Slide

  13. Example
    Accept: application/json, application/xml;q=0.9, text/html;q=0.8,
    text/*;q=0.7, */*;q=0.5
    Priority Description
    q=1.0 application/json
    q=0.9 application/xml
    q=0.8 text/html
    q=0.7 text/* (ie. any text)
    q=0.5 */* (ie. any media type)

    View Slide

  14. HATEOAS
    Hypermedia As The Engine Of Application State.
    It means that hypermedia should be used to find your
    way through the API. It is all about state transitions.
    Your application is just a big state machine.








    View Slide

  15. So much for the REST theory ...

    View Slide

  16. Let's Meet the PHP World!

    View Slide

  17. In order to comply with RMM Level 1,
    I want to turn business objects/data to resources.

    View Slide

  18. JMS Serializer
    ­ schmittjoh/serializer

    View Slide

  19. In A Nutshell
    Enables (de)
    serialization of object graphs
    Implements visitor pattern to enable flexibility
    Fully leverage native JSON and XML
    Custom
    exclusion strategies
    to determine what to serialize
    Quite easy to extend

    View Slide

  20. Usage
    use JMS\SerializerBundle\Annotation as Serializer;
    /** @Serializer\XmlRoot("response") */
    class MyResponse
    {
    /** * @Serializer\XmlList(inline=true, entry="article") */
    protected $articles;
    /** * @Serializer\XmlAttribute() */
    protected $page;
    public function __construct(Collection $articles, $page)
    {
    $this->articles = $articles;
    $this->page = $page;
    }
    }

    View Slide

  21. JSON
    {
    "articles": [
    "bim",
    "bam",
    "bingo"
    ],
    "page": "2"
    }

    View Slide

  22. XML

    bim
    bam
    bingo

    View Slide

  23. In order to comply with RMM Level 2,
    I want to use proper
    HTTP verbs
    and
    status codes
    .

    View Slide

  24. But I need to understand what the client wants and says.
    And I also need to rely on a serializer.
    And I will have to deal with filters, versioning, etc.
    I need a framework!

    View Slide

  25. FOSRestBundle (+ Symfony)
    ­ FriendsOfSymfony/FOSRestBundle

    View Slide

  26. In A Nutshell
    Toolbox of services and listeners to build RESTful APIs
    Generate HTML, XML, and JSON from a single action
    Automatic generation of routes from actions
    GET parameter parsing and validation
    Integration with Symfony serializer and JMS Serializer
    Accept header negotiation (thx to the lib)
    Request body decoding
    API versioning
    Negotiation

    View Slide

  27. Usage
    class RestController
    {
    /**
    * route name: liip_hello_rest_get_articles
    * pattern: /liip/hello/rest/articles.{_format}
    * http method requirement: GET
    *
    * @View()
    * @QueryParam(name="page", requirements="\d+", default="1")
    */
    public function getArticlesAction($page)
    {
    //$page = $request->query->get('page');
    //$page = preg_match('\d+', $page) ? $page : 1;
    $articles = [ 'bim', 'bam', 'bingo' ];
    return new \Acme\MyBundle\MyResponse($articles, $page);
    }
    }

    View Slide

  28. HTML



    bim
    bam
    bingo

    page: 2


    View Slide

  29. Content Type Negotiation
    fos_rest:
    format_listener:
    rules:
    -
    path: ^/
    priorities: [ html, json, xml ]
    fallback_format: ~
    prefer_extension: true
    Accept: application/json, application/xml;q=0.9, text/html;q=0.8,
    text/*;q=0.7, */*;q=0.5
    ­ willdurand/Negotiation

    View Slide

  30. Version Listener
    fos_rest:
    view:
    mime_types:
    json:
    - 'application/json'
    - 'application/json;version=1.0'
    - 'application/json;version=1.1'
    format_listener:
    media_type:
    version_regex: '/(v|version)=(?P[0-9\.]+)/'
    Accept: application/json;version=1.1
    class User
    {
    /** @Until("1.0") */
    private $name;
    /** @Since("1.1") */
    private $lastname;
    }

    View Slide

  31. Message Decoding
    Decode request body from XML, JSON, etc. into the request
    Integration with converters to turn parameters to objects
    GET parameter validation and normalization
    POST /users HTTP/1.1
    Accept: application/json,*/*;q=0.8
    Content-Type: application/json
    {"name":"Don Johnson"}
    public functon postAction(Request $request)
    {
    $name = $request->request->get('name');
    }

    View Slide

  32. Allowed Methods Listener
    Examines all available routes to determine what HTTP
    methods are available for the request URI
    Automatically sets an according Allow header in the response
    fos_rest:
    allowed_methods_listener: true
    HTTP/1.1 200 OK
    Allow: GET, POST

    View Slide

  33. MIME Type Listener
    Register MIME types that Symfony2's Request class does not
    support by default
    fos_rest:
    view:
    mime_types:
    json: ['application/json', 'application/vnd.example-com.foo+json']
    rss: 'application/rss+xml'
    jpg: 'image/jpeg'
    png: 'image/png'
    jsonp_handler: ~
    p JSONP callback validation thanks to the
    library
    willdurand/JsonpCallbackValidator

    View Slide

  34. Error/Exception Handling
    Return correct HTTP status code
    Determine for which exception to expose the exception
    message
    Automatically extract errors from a Form instance
    fos_rest:
    exception:
    codes:
    'Symfony\Component\Routing\Exception\ResourceNotFoundException': 404
    'Doctrine\ORM\OptimisticLockException': HTTP_CONFLICT
    messages:
    'Symfony\Component\Routing\Exception\ResourceNotFoundException': true

    View Slide

  35. Want A Smarter Mapping?
    ­ TheBigBrainsCompany/rest-util

    View Slide

  36. Usage
    $invalidArgumentExceptionMapping = new ExceptionMapping(array(
    'exceptionClassName' => '\InvalidArgumentException',
    'factory' => 'default',
    'httpStatusCode' => 400,
    'errorCode' => 400101,
    'errorMessage' => null,
    'errorExtendedMessage' => 'Extended message',
    'errorMoreInfoUrl' => 'http://api.my.tld/doc/error/400101',
    ));
    $exceptionMap = new ExceptionMap();
    $exceptionMap->add($invalidArgumentExceptionMapping);
    $errorResolver = new ErrorResolver($exceptionMap);
    $error = $errorResolver->resolve(
    new \InvalidArgumentException('This is an invalid argument exception.')
    );

    View Slide

  37. Symfony Bundle Configuration
    tbbc_rest_util:
    error:
    exception_mapping:
    InvalidArgumentException:
    class: "InvalidArgumentException"
    factory: default
    http_status_code: 400
    error_code: 400101
    error_message: ~
    extended_message: "Extended message"
    more_info_url: "http://api.my.tld/doc/error/400101"

    View Slide

  38. Example
    print_r($error->toArray());
    Array
    (
    [http_status_code] => 400
    [code] => 400101
    [message] => This is an invalid argument exception.
    [extended_message] => Extended message
    [more_info_url] => http://api.my.tld/doc/error/400101
    )
    echo json_encode($error->toArray());
    {
    "http_status_code": 400,
    "code": 400101,
    "message": "This is an invalid argument exception.",
    "extended_message": "Extended message",
    "more_info_url": "http:\/\/api.my.tld\/doc\/error\/400101"
    }

    View Slide

  39. In order to comply with RMM Level 3,
    I want to HATEOAS ALL THE THINGS!

    View Slide

  40. Hateoas
    ­ willdurand/Hateoas

    View Slide

  41. In A Nutshell
    Leverages the JMS Serializer library
    Relies on the Symfony2 ExpressionLanguage component
    Supports JSON and XML
    Allows to configure links and embedded resources in
    XML, YAML, PHP, or Annotations
    Dynamic relations (relation providers)
    Exclusion strategies

    View Slide

  42. Usage
    use JMS\Serializer\Annotation as Serializer;
    use Hateoas\Configuration\Annotation as Hateoas;
    /**
    * @Serializer\XmlRoot("user")
    *
    * @Hateoas\Relation("self", href = "expr('/api/users/' ~ object.getId())")
    */
    class User
    {
    /** @Serializer\XmlAttribute */
    private $id;
    private $firstName;
    private $lastName;
    public function getId() {}
    }

    View Slide

  43. JSON
    $hateoas = HateoasBuilder::create()->build();
    $json = $hateoas->serialize(new User(123, 'John', 'Doe'), 'json');
    {
    "id": 123,
    "first_name": "John",
    "last_name": "Doe",
    "_links": {
    "self": {
    "href": "/api/users/123"
    }
    }
    }

    View Slide

  44. XML
    $hateoas = HateoasBuilder::create()->build();
    $xml = $hateoas->serialize(new User(123, 'John', 'Doe'), 'xml');







    View Slide

  45. In order to make my API more self-documenting,
    I want to use URI Templates.
    fancy

    View Slide

  46. RFC 6570: URI Template
    A compact sequence of characters for describing
    a range of URIs through variable expansion.
    URIs URI Template
    http://example.com/~fred/ http://example.com/~
    {username}/
    http://example.com/~mark/

    View Slide

  47. TemplatedUriRouter
    ­ hautelook/TemplatedUriRouter

    View Slide

  48. Usage
    demo_route:
    pattern: /demo/{page}
    $templateLink = (new Rfc6570Generator($routes))
    ->generate('demo_route', array(
    'page' => '{page}',
    'sort' => '{sort}',
    'filter' => array('{filter}'),
    ));
    /demo/{page}?{&sort}{&filter%5B%5D*}
    p Leverages the .
    Symfony Routing component

    View Slide

  49. In order to [make humans happy|dominate the world|whatever],
    I want to create documentation for my API.

    View Slide

  50. Swagger UI
    ­ wordnik/swagger-ui

    View Slide

  51. NelmioApiDocBundle
    ­ nelmio/NelmioApiDocBundle

    View Slide

  52. In A Nutshell
    Designed for Symfony
    Generates documentation for your REST APIs
    Gathers information from PHPDoc
    Supports FOSRestBundle, SensioFrameworkExtraBundle,
    JMSSerializerBundle and JMSSecurityExtraBundle
    Supports your own annotations and you own parsers
    Sandbox (Killer Feature 쁥)
    Swagger compliant

    View Slide

  53. Usage
    /**
    * List all notes.
    *
    * @ApiDoc(
    * resource = true,
    * statusCodes = { 200 = "Returned when successful" }
    * )
    * @QueryParam(
    * name="offset", requirements="\d+", nullable=true,
    * description="Offset from which to start listing notes."
    * )
    * @QueryParam(
    * name="limit", requirements="\d+", default="5",
    * description="How many notes to return."
    * )
    */
    public function getNotesAction() {}

    View Slide

  54. View Slide

  55. ng-admin
    ­ marmelab/ng-admin

    View Slide

  56. View Slide

  57. In order to not burn too many servers,
    I want to add and manage a caching layer.

    View Slide

  58. FOSHttpCache
    ­ FriendsOfSymfony/FOSHttpCache

    View Slide

  59. In A Nutshell
    Set path-based cache expiration headers via app config
    Set up an invalidation scheme without writing PHP code
    Tag your responses and invalidate cache based on tags
    Send invalidation requests with minimal performance impact
    (
    Varnish
    and
    Nginx
    supported out of the box)
    Differentiate caches based on user type (e.g. roles)
    Easily implement HTTP cache client

    View Slide

  60. Demo?

    View Slide

  61. ­ willdurand/Propilex

    View Slide

  62. ­ gimler/symfony-rest-edition

    View Slide

  63. Thank You.
    Questions?
    ½
    ­
    «
    williamdurand.fr
    github.com/willdurand
    twitter.com/couac

    View Slide

  64. https://knpuniversity.com/screencast/rest

    View Slide

  65. Further Reading
    http://www.ietf.org/rfc/rfc2616.txt
    http://martinfowler.com/articles/richardsonMaturityModel.html
    http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
    http://www.slideshare.net/Wombert/designing-http-interfaces-and-restful-web-services-dpc2012-20120608
    http://williamdurand.fr/2012/08/02/rest-apis-with-symfony2-the-right-way/
    http://www.mnot.net/blog/2012/12/04/api-evolution
    http://knpuniversity.com/blog/what-the-rest
    http://knpuniversity.com/blog/rest-revisited
    http://timelessrepo.com/haters-gonna-hateoas
    http://blog.liip.ch/archive/2013/10/28/resting-with-symfony2.html
    http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/

    View Slide

  66. OAuth

    View Slide

  67. What Is OAuth?
    OAuth is an open protocol to allow secure authorization
    in a simple and standard method from web, mobile
    and desktop applications.
    It is an authorization framework that enables a third-party
    application to obtain limited access to an HTTP service.
    http://edu.williamdurand.fr/security-slides/#slide85

    View Slide

  68. HWIOAuthBundle
    Client-side implementation
    Support 20+ different providers
    Supports both OAuth1.0a and OAuth2
    ­ hwi/HWIOAuthBundle

    View Slide

  69. FOSOAuthServerBundle
    Server-side implementation of OAuth2
    Supports Doctrine ORM|ODM, Propel
    Highly configurable
    Thank you for helping us!
    Alan Gabriel Bem
    ­
    ­ (OAuth1.0a)
    FriendsOfSymfony/FOSOAuthServerBundle
    willdurand/BazingaOAuthServerBundle

    View Slide