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

PHP Benelux - Build Restful API easily with Symfony

PHP Benelux - 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

January 29, 2016
Tweet

More Decks by Sarah KHALIL

Other Decks in Technology

Transcript

  1. Build Restful API easily
    with Symfony Sarah Khalil - January 29th, 2016

    View full-size slide

  2. Who am I?
    • Head of
    • Trainer & Developer
    • Enjoying sharer
    Sarah Khalil
    @saro0h

    View full-size slide

  3. What’s the plan?

    View full-size slide

  4. What’s the plan?
    Back to basics
    1

    View full-size slide

  5. What’s the plan?
    Back to basics
    1
    How to do it?
    2

    View full-size slide

  6. What’s the plan?
    Back to basics
    1
    Alternative tools
    3
    How to do it?
    2

    View full-size slide

  7. What is REST?

    View full-size slide

  8. What is REST?
    • REpresentational State Transfer

    • Architecture (not a protocol!)

    • Associated to Service Oriented Architecture

    • Management of resources

    View full-size slide

  9. The 6 constraints of
    REST

    View full-size slide

  10. Client - Server (1/6)
    Separation of concerns

    Portability

    View full-size slide

  11. Stateless (2/6)
    Based on HTTP

    Session has to be held on the client side

    View full-size slide

  12. Cacheable (3/6)
    Avoid useless requests

    View full-size slide

  13. Layered system (4/6)
    The client doesn’t know the system behind

    View full-size slide

  14. Uniform interface (5/6)
    Each resource has its identifier: the URI.

    Each resource has its representation.

    Auto-descripted message:

    for instance, JSON = content-type
    header.

    View full-size slide

  15. Code on demand (6/6)
    The client download some code and executes it

    The server is not aware of what is happening
    anymore
    optional

    View full-size slide

  16. HTTP Methods
    • GET

    • POST

    • PUT

    • PATCH
    • DELETE

    • OPTIONS

    • CONNECT

    • HEAD

    View full-size slide

  17. Keep’em in mind
    JSON, XML, HTML…

    By developers and for developers

    Scalability

    Latency

    Security
    !

    View full-size slide

  18. http://martinfowler.com

    View full-size slide

  19. HATEOAS
    Hypermedia as the engine of Application State

    Discoverability => Links!

    View full-size slide

  20. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View full-size slide

  21. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View full-size slide

  22. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View full-size slide

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

    View full-size slide

  24. Response
    HTTP/1.1 200 OK
    Host: 127.0.0.1:8000
    Content-Type: text/html; charset=UTF-8



    View full-size slide

  25. Symfony\Component\HttpFoundation\Request
    Symfony\Component\HttpFoundation\Response

    View full-size slide

  26. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View full-size slide

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

    View full-size slide

  28. Json/XML Object
    serialization
    deserialization

    View full-size slide

  29. Configuration
    XML, YML and Annotation.

    http://jmsyst.com/libs/serializer/master/reference

    Helper to serialize properties the way you need.

    View full-size slide

  30. 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’)
    );
    }
    }

    View full-size slide

  31. 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

    View full-size slide

  32. Exclusion policy
    /**
    * @JMS\ExclusionPolicy("all")
    */
    class Product
    {
    /**
    * @JMS\Expose()
    */
    private $name;
    private $users;
    }
    • Visitor pattern

    • Object graph

    • Relations (xToMany)

    View full-size slide

  33. Serializer handler
    Specific needs

    Implements JMS\Serializer\Handler\SubscribingHandlerInterface

    View full-size slide

  34. 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',
    ],
    ];
    }

    View full-size slide

  35. 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;
    }

    View full-size slide

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

    View full-size slide

  37. 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',
    ),
    );
    }
    }

    View full-size slide

  38. Event subscriber
    name="jms_serializer.event_subscriber"
    type="AppBundle\MyClass"
    direction="serialization"
    format="json"
    method="onPostSerialize"
    />

    View full-size slide

  39. Other features
    Serialization group

    Versionning

    View full-size slide

  40. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View full-size slide

  41. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle
    Setup your Symfony
    application

    View full-size slide

  42. Controller
    # app/config/config.yml
    fos_rest:
    view:
    view_response_listener: force
    sensio_framework_extra:
    view: { annotations: false }
    /**
    * @View()
    */
    public function getUsersAction()
    {

    return $data;
    }

    View full-size slide

  43. Content-Type negociation
    • Request => Accept
    priorities of media-type(s) that your application
    is ready to work with.

    • Response => Content-Type
    the media type of the response (html, json,
    png…).

    View full-size slide

  44. Content-Type negociation
    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

    View full-size slide

  45. Request body converter
    Decoder

    ParamConverter
    /**
    * @ParamConverter("post", converter="fos_rest.request_body")
    */
    public function updateAction(Post $post)
    {
    }
    # app/config/config.yml
    sensio_framework_extra:
    request: { converters: true }
    fos_rest:
    body_converter:
    enabled: true

    View full-size slide

  46. Validation (with the Symfony Validator component)

    Configuration
    fos_rest:
    body_converter:
    enabled: true
    validate: true
    validation_errors_argument: validationErrors

    View full-size slide

  47. 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
    }
    }

    View full-size slide

  48. Param Fetcher

    View full-size slide

  49. /**
    * @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

    View full-size slide

  50. /**
    * @QueryParam(name="page", requirements="\d+", default="1")
    */
    public function getArticlesAction($page)
    {}
    fos_rest.param_fetcher_listener: force

    View full-size slide

  51. Error handling

    View full-size slide

  52. Error handling

    Option 1
    fos_rest:
    codes:
    'Symfony\Component\Routing\Exception\ResourceNotFoundException': 404
    'Doctrine\ORM\OptimisticLockException': HTTP_CONFLICT

    View full-size slide

  53. fos_rest:
    view:
    exception_wrapper_handler: My\Bundle\Handler\MyExceptionWrapperHandler
    1
    Error handling

    Option 2

    View full-size slide

  54. fos_rest:
    view:
    exception_wrapper_handler: My\Bundle\Handler\MyExceptionWrapperHandler
    1
    implements FOS\RestBundle\ViewExceptionWrapperHandlerInterface
    2
    Error handling

    Option 2

    View full-size slide

  55. fos_rest:
    view:
    exception_wrapper_handler: My\Bundle\Handler\MyExceptionWrapperHandler
    1
    implements FOS\RestBundle\ViewExceptionWrapperHandlerInterface
    2
    public function wrap($data)
    {
    return new MyExceptionWrapper($data);
    }
    3
    Error handling

    Option 2

    View full-size slide

  56. fos_rest:
    view:
    exception_wrapper_handler: My\Bundle\Handler\MyExceptionWrapperHandler
    Take example on the FOS\RestBundle\Util\ExceptionWrapper
    1
    implements FOS\RestBundle\ViewExceptionWrapperHandlerInterface
    2
    public function wrap($data)
    {
    return new MyExceptionWrapper($data);
    }
    3
    Error handling

    Option 2

    View full-size slide

  57. Other features
    • Automatic generation of routes

    • Consistency

    • Versioning

    • url

    • mime type

    And way more!

    http://symfony.com/doc/master/bundles/FOSRestBundle/index.html

    View full-size slide

  58. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View full-size slide

  59. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle
    Unlock the level 3 of the Richardson’s model

    View full-size slide

  60. HAL
    • Hypertext Application Language

    • _links - relations

    • _embedded - related resource(s)

    • self
    • Specifications: http://stateless.co/hal_specification.html

    View full-size slide

  61. JSON API
    • Similar to HAL

    • meta - meta info such as pagination…

    • links - Relations (xToOne - xToMany)

    • linked - Resource(s) linked

    • Specifications : http://jsonapi.org/format/

    View full-size slide

  62. Configuration
    Annotation, XML and YML

    Relies on Expression Language (since Symfony 2.4)

    View full-size slide

  63. /**
    * @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
    Expression Language

    View full-size slide

  64. Result
    {
    "id": 1,
    "_links": {
    "self": {
    "href": "http://domaine.name/users/eh1754329986/notifications/1/"
    }
    }
    }

    View full-size slide

  65. /**
    * …
    * @Hateoas\Relation(
    * "activity",
    * href = "expr(‘/activity/'~object.getActivity().getId()",
    * embedded = "expr(object.getActivity())",
    * exclusion = @Hateoas\Exclusion(excludeIf = "expr(object.getActivity() === null)")
    * )
    */
    class Notification

    View full-size slide

  66. Result
    {
    "id": 1,
    "_embedded": {
    "activity": {
    "id": 3,
    "content": "Activity content",
    "_links": {
    "self": {
    "href": "/activities/3"
    }
    }
    }
    }
    }

    View full-size slide

  67. Representation
    Use representation to decorate your resource

    Hateoas\Representation\PaginatedRepresentation
    And many more:

    https://github.com/willdurand/Hateoas/tree/master/src/Hateoas/Representation

    View full-size slide

  68. class CollectionRepresentation
    {
    private $resources;
    public function __construct($resources) {
    $this->resources = $resources;
    }
    public function getResources()
    {
    return $this->resources;
    }
    }

    View full-size slide

  69. "resources": {
    {
    "name": « Mercure Los Angeles"
    },
    {
    "name": "Novotel Mexico"
    },
    }

    View full-size slide

  70. Representation
    Any representation you need!

    A class with the properties you need

    Let the serializer do its job

    View full-size slide

  71. "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"
    }
    }

    View full-size slide

  72. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle
    Guzzle

    View full-size slide

  73. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle
    Guzzle
    Communicate between
    microservices

    View full-size slide

  74. Frontend
    Service 1
    Service 2
    Service 3
    Service 4
    Private network
    Client

    View full-size slide

  75. Guzzle Bundle
    HTTP Client

    Request

    Response

    HTTPS

    Error management
    • https://github.com/misd-
    service-development/guzzle-
    bundle
    • https://github.com/csarrazi/
    CsaGuzzleBundle

    View full-size slide

  76. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View full-size slide

  77. Authentication & Authorization

    View full-size slide

  78. Authentication & Authorization

    View full-size slide

  79. Authentication & Authorization

    View full-size slide

  80. Authentication with
    multiple API’s

    View full-size slide

  81. Service 1
    Service 2
    Client
    Frontend 2
    Frontend 1
    Service 3
    Service 4

    View full-size slide

  82. Workflow
    1. The user authenticates [him|her]self
    on frontend 1
    2. Do some stuff
    3. Then clicks on a link that brings him/
    her to the frontend 2

    View full-size slide

  83. Service 1
    Service 2
    Client
    Frontend 2
    Frontend 1
    Service 3
    Service 4
    GET /admin HTTP/1.1
    User-Agent: curl/7.37.1
    Host: 127.0.0.1:8000
    Accept: */*
    Authorization: Bearer XXXXXX

    View full-size slide

  84. Get the token
    In an authenticator, grab the
    Authorization header value

    View full-size slide

  85. Get the token
    public function createToken(Request $request, $providerKey)
    {
    $bearer = $request->headers…
    }

    View full-size slide

  86. Validate a token / Get a new one…

    View full-size slide

  87. Authentication
    • Authorization header: Authorization: Bearer
    • https://github.com/poledev/katas/tree/kata-
    authentication
    • http://symfony.com/doc/current/cookbook/security/
    api_key_authentication.html
    • Make it stateless!

    View full-size slide

  88. Authorization
    • access_control

    • $this->get(‘security.authorization_checker’)->isGranted('ROLE_ADMIN')
    • {% if is_granted(‘ROLE_ADMIN’) %}
    • Voter

    • …

    View full-size slide

  89. FosHttpCacheBundle
    Keep it mind

    View full-size slide

  90. Ease the usage of your
    API

    View full-size slide

  91. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View full-size slide

  92. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle
    Documentation

    View full-size slide

  93. Alternative to
    FOSRestBundle

    View full-size slide

  94. https://api-platform.com/

    View full-size slide

  95. http://tinyurl.com/sfliveparis

    View full-size slide

  96. Thank you!
    @saro0h
    speakerdeck.com/saro0h/
    github.com/saro0h/oauth-github
    Follow me on
    @catlannister
    Come see us at the SensioLabs booth!

    View full-size slide