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. Build Restful API easily
    with Symfony Sarah Khalil - October 21st, 2015

    View Slide

  2. Who I am?
    • Head of
    • Trainer & Developer
    • Enjoying sharer
    • Contributor to

    View Slide

  3. View Slide

  4. What is REST?

    View Slide

  5. What is REST?
    • REpresentational State Transfer
    • Architecture (not a protocol!)
    • Associated to Service Oriented Architecture
    • Management of resources

    View Slide

  6. The 6 constraints of
    REST

    View Slide

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

    • Portability.

    View Slide

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

    • Session has to be held on the client side.

    View Slide

  9. Cacheable (3/6)
    • Avoid useless requests.

    View Slide

  10. Layered system (4/6)
    • The client requests a resource but it doesn’t know the
    system behind.

    View Slide

  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.

    View Slide

  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.

    View Slide

  13. HTTP Methods
    • GET

    • POST

    • PUT

    • PATCH
    • DELETE

    • OPTIONS

    • CONNECT

    • HEAD

    View Slide

  14. Restful API

    View Slide

  15. Keep’em in mind
    • JSON, XML, HTML…
    • By developers and for developers
    • Scalability
    • Latency
    • Security
    !

    View Slide

  16. http://martinfowler.com

    View Slide

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

    View Slide

  18. View Slide

  19. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View Slide

  20. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View Slide

  21. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View Slide

  22. Philosophy

    View Slide

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

    View Slide

  24. Symfony\Component\HttpFoundation\Request

    View Slide

  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



    View Slide

  26. Symfony\Component\HttpFoundation
    \Response

    View Slide

  27. HttpKernel

    View Slide

  28. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View Slide

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

    View Slide

  30. View Slide

  31. Json/XML Object
    serialization
    deserialization

    View Slide

  32. Configuration
    • XML, YML and Annotation.
    • http://jmsyst.com/libs/serializer/master/reference
    • Helper to serialize properties the way you need.

    View Slide

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

    View Slide

  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

    View Slide

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

    • Object graph

    • Relations (xToMany)

    View Slide

  36. Serializer handler
    • Specific needs
    • implements JMS\Serializer\Handler\SubscribingHandlerInterface

    View Slide

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

    View Slide

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

    View Slide

  39. Event subscriber
    • http://jmsyst.com/libs/serializer/master/event_system
    • implements JMS\Serializer\EventDispatcher\EventSubscriberInterface
    name="jms_serializer.event_subscriber"
    type="AppBundle\MyClass"
    direction="serialization"
    format="json"
    method="onPostSerialize"
    />

    View Slide

  40. serialization
    deserialization

    View Slide

  41. Object
    serialization
    deserialization

    View Slide

  42. Object
    serialization
    deserialization
    serializer.pre_serialize

    View Slide

  43. Object
    serialization
    deserialization
    serializer.pre_serialize

    View Slide

  44. Object
    serialization
    deserialization
    serializer.pre_serialize
    serializer.post_serialize

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  50. Other features
    • Serialization group
    • Versionning

    View Slide

  51. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View Slide

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

    View Slide

  53. View Slide

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

    View Slide

  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…).

    View Slide

  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

    View Slide

  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)
    {
    }

    View Slide

  58. Validation

    View Slide

  59. Validation (with the Symfony Validator component)
    Configuration
    fos_rest:
    body_converter:
    enabled: true
    validate: true
    validation_errors_argument: validationErrors

    View Slide

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

    View Slide

  61. Param Fetcher

    View Slide

  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",
    * [email protected]
    * )
    */
    public function getArticlesAction(ParamFetcher $paramFetcher)
    {}
    http://mydomain.com/resources?sort=desc
    In $request->request ($_POST)
    fos_rest.param_fetcher_listener: true

    View Slide

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

    View Slide

  64. Error handling

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  68. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View Slide

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

    View Slide

  70. View Slide

  71. HAL
    • Hypertext Application Language
    • _links - relations
    • _embedded - related resource(s)
    • self
    • Specifications: http://stateless.co/hal_specification.html

    View Slide

  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/

    View Slide

  73. Configuration
    • Annotation, XML and YML
    • Relies on Expression Language (since Symfony 2.4)

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  78. Representation
    • Use representation to decorate your resource
    • Hateoas\Representation\PaginatedRepresentation
    • And many more:
    https://github.com/willdurand/Hateoas/tree/master/src/Hateoas/Representation

    View Slide

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

    View Slide

  80. Representation
    • Any representation you need !
    • A class with the properties you need.
    • Let the serializer do its job.

    View Slide

  81. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle
    Guzzle

    View Slide

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

    View Slide

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

    View Slide

  84. Guzzle Bundle
    • HTTP Client
    • Request
    • Response
    • HTTPS
    • Error management
    • https://github.com/misd-
    service-development/guzzle-
    bundle
    • https://github.com/csarrazi/
    CsaGuzzleBundle

    View Slide

  85. View Slide

  86. View Slide

  87. Security

    View Slide

  88. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View Slide

  89. 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 Slide

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

    View Slide

  91. FosHttpCacheBundle
    Keep it mind

    View Slide

  92. View Slide

  93. One last thing

    View Slide

  94. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle

    View Slide

  95. Symfony Full Stack
    JMSSerializerBundle
    FOSRestBundle
    BazingaHateoasBundle NelmioApiDocBundle
    Documentation

    View Slide

  96. View Slide

  97. View Slide

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

    View Slide

  99. http://tinyurl.com/sfcon2015

    View Slide

  100. http://tinyurl.com/sfliveusa

    View Slide

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

    View Slide