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

Building RESTful APIs with Symfony Components

Building RESTful APIs with Symfony Components

Built around the HTTP specification, Symfony components provide a rock solid foundation for building RESTful APIs, whether we are using the full framework or not. In this session we will see how to successfully deal with problems such as how to adapt our models to the desired representations back and forth, authentication strategies, some tips and tricks to test effectively our APIs and also how to apply these concepts to migrate effectively a legacy application, based upon real use cases.

Victoria Quirante

February 16, 2017
Tweet

More Decks by Victoria Quirante

Other Decks in Programming

Transcript

  1. Building RESTful APIs
    with Symfony Components
    Victoria Quirante 16-17th Feb. 2017

    View full-size slide

  2. I work at Limenius
    We build tailor-made projects with
    Symfony and React
    Most projects require the
    implementation of an API
    Symfony Components provide a
    rock-solid foundation for building APIs
    Victoria Quirante
    @vicqr
    [email protected]

    View full-size slide

  3. I. INTRODUCTION

    View full-size slide

  4. Why would I want my API
    to be RESTful?

    View full-size slide

  5. Possible reasons AGAINST
    Perhaps I can think of a better way to structure an API

    View full-size slide

  6. Possible reasons AGAINST
    Perhaps I can think of a better way to structure an API
    REST seems very controversial and confusing in some points

    View full-size slide

  7. Main reasons to go for REST
    REST makes the most of HTTP

    View full-size slide

  8. Main reasons to go for REST
    REST makes the most of HTTP
    It means to have a common language

    View full-size slide

  9. Main reasons to go for REST
    REST makes the most of HTTP
    It means to have a common language
    These are very powerful reasons

    View full-size slide

  10. What you already know about REST

    View full-size slide

  11. Sane way to approach REST

    View full-size slide

  12. Sane way to approach REST
    1. Learn the stuff that is commonly accepted

    View full-size slide

  13. Sane way to approach REST
    1. Learn the stuff that is commonly accepted
    Use nouns for
    the resources!

    View full-size slide

  14. Sane way to approach REST
    1. Learn the stuff that is commonly accepted
    Use nouns for
    the resources!
    Use HTTP verbs!

    View full-size slide

  15. Sane way to approach REST
    1. Learn the stuff that is commonly accepted
    Use HTTP verbs!
    Use nouns for
    the resources!
    Return meaningful
    status codes!

    View full-size slide

  16. Sane way to approach REST
    1. Learn the stuff that is commonly accepted
    2. Be aware of the grey areas / controversial points

    View full-size slide

  17. Sane way to approach REST
    1. Learn the stuff that is commonly accepted
    2. Be aware of the grey areas / controversial points
    Do I have to return the
    created/updated resource?

    View full-size slide

  18. Sane way to approach REST
    1. Learn the stuff that is commonly accepted
    2. Be aware of the grey areas / controversial points
    Do I have to return the
    created/updated resource?
    Can I have /recipes.json and
    /recipes.html?

    View full-size slide

  19. Sane way to approach REST
    “Remember the "RE" in REST stands for "REpresentational" which means we are talking about representation of state.
    Therefore a single URI should be used by resource as the unique identifier. Instead to get different representations we
    should use media types.”
    Lukas K. Smith (FOSRestBundle author)
    http://pooteeweet.org/blog/2248
    Can I have /recipes.json and
    /recipes.html?

    View full-size slide

  20. Sane way to approach REST
    “Remember the "RE" in REST stands for "REpresentational" which means we are talking about representation of state.
    Therefore a single URI should be used by resource as the unique identifier. Instead to get different representations we
    should use media types.”
    Lukas K. Smith (FOSRestBundle author)
    http://pooteeweet.org/blog/2248
    “Section 6.2.1 does not say that content negotiation should be used all the time.”
    Roy T. Fielding (author of REST thesis and REST concept itself)
    https://groups.yahoo.com/neo/groups/rest-discuss/conversations/messages/5857
    Can I have /recipes.json and
    /recipes.html?

    View full-size slide

  21. Sane way to approach REST
    “Remember the "RE" in REST stands for "REpresentational" which means we are talking about representation of state.
    Therefore a single URI should be used by resource as the unique identifier. Instead to get different representations we
    should use media types.”
    Lukas K. Smith (FOSRestBundle author)
    http://pooteeweet.org/blog/2248
    “Section 6.2.1 does not say that content negotiation should be used all the time.”
    Roy T. Fielding (author of REST thesis and REST concept itself)
    https://groups.yahoo.com/neo/groups/rest-discuss/conversations/messages/5857
    Can I have /recipes.json and
    /recipes.html?

    View full-size slide

  22. Sane way to approach REST
    1. Learn the stuff that is commonly accepted
    2. Be aware of the grey areas / controversial points
    3. Choose a side in those…

    View full-size slide

  23. Sane way to approach REST
    1. Learn the stuff that is commonly accepted
    2. Be aware of the grey areas / controversial points
    3. Choose a side in those… … and stick to it

    View full-size slide

  24. Sane way to approach REST
    1. Learn the stuff that is commonly accepted
    2. Be aware of the grey areas / controversial points
    3. Choose a side in those… … and stick to it
    Be civilized and consistent

    View full-size slide

  25. Why Symfony & REST?

    View full-size slide

  26. Why Symfony & REST?
    REST is several things:
    - A very strict definition -that not many people fully understand
    - A battlefield
    - A way to make the most of HTTP

    View full-size slide

  27. Why Symfony & REST?
    REST is several things:
    - A very strict definition -that not many people fully understand
    - A battlefield
    - A way to make the most of HTTP
    “I don't like MVC because that's not how the web works. Symfony2 is an HTTP framework; it is
    a Request/Response framework. That's the big deal. The fundamental principles of Symfony2
    are centered around the HTTP specification.”
    Fabien Potencier

    View full-size slide

  28. Why Symfony & REST?
    REST ♥ HTTP
    Symfony ♥ HTTP

    View full-size slide

  29. Why Symfony
    Components?

    View full-size slide

  30. Symfony is two things
    1. A full stack framework

    View full-size slide

  31. Symfony is two things
    1. A full stack framework
    2. A set of independent
    components

    View full-size slide

  32. What are Symfony Components
    Set of decoupled and reusable PHP libraries
    You can use any in your own applications independently from the
    Symfony Framework
    Many popular PHP projects do so

    View full-size slide

  33. Components used in popular projects
    http://symfony.com/projects

    View full-size slide

  34. Doctrine
    http://symfony.com/projects

    View full-size slide

  35. Propel
    http://symfony.com/projects

    View full-size slide

  36. Silex
    http://symfony.com/projects

    View full-size slide

  37. Drupal
    http://symfony.com/projects

    View full-size slide

  38. Laravel
    http://symfony.com/projects

    View full-size slide

  39. Components that we are going to see in detail
    HttpFoundation
    Serializer
    Validator
    Form
    Guard

    View full-size slide

  40. Where/when can you apply this knowledge
    - Working with the full Symfony framework

    View full-size slide

  41. Where/when can you apply this knowledge
    - Working with the full Symfony framework
    - Using some of these components in some other framework

    View full-size slide

  42. Where/when can you apply this knowledge
    - Working with the full Symfony framework
    - Using some of these components in some other framework
    - Writing your own framework

    View full-size slide

  43. Where/when can you apply this knowledge
    - Working with the full Symfony framework
    - Using some of these components in some other framework
    - Writing your own framework
    https://symfony.com/doc/current/create_framework/index.html

    View full-size slide

  44. See repository
    https://github.com/VictoriaQ/rest-symfony-components

    View full-size slide

  45. II. SYMFONY COMPONENTS

    View full-size slide

  46. HttpFoundation
    Moving to an Object-Oriented
    approach
    HttpFoundation
    Serializer
    Validator
    Form
    Guard

    View full-size slide

  47. HttpFoundation - The idea
    Defines an object-oriented layer for the HTTP specification

    View full-size slide

  48. HttpFoundation - The idea
    In PHP:
    - Request represented by global variables
    - Response generated by some functions
    Defines an object-oriented layer for the HTTP specification

    View full-size slide

  49. HttpFoundation - The idea
    In PHP:
    - Request represented by global variables
    - Response generated by some functions
    HttpFoundation replaces global variables and functions by an object-oriented layer:
    - $_GET, $_POST, $_FILES, $_COOKIE... ----> Request()
    - echo(), header(), setcookie()... ----> Response()
    Defines an object-oriented layer for the HTTP specification

    View full-size slide

  50. HttpFoundation - Request()
    Holds information about the client request
    use Symfony\Component\HttpFoundation\Request;
    $request = Request::createFromGlobals();

    View full-size slide

  51. HttpFoundation - Request()
    Creates Request object based on current PHP global variables
    use Symfony\Component\HttpFoundation\Request;
    $request = Request::createFromGlobals();

    View full-size slide

  52. HttpFoundation - Request()
    Creates Request object based on current PHP global variables
    use Symfony\Component\HttpFoundation\Request;
    $request = new Request(
    $_GET,
    $_POST,
    array(),
    $_COOKIE,
    $_FILES,
    $_SERVER
    );

    View full-size slide

  53. HttpFoundation - Response()
    Holds information of what needs to be sent back to the client
    use Symfony\Component\HttpFoundation\Response;
    $response = new Response(
    'Content',
    Response::HTTP_OK,
    array('content-type' => 'text/html')
    );

    View full-size slide

  54. Let’s create a POST endpoint
    When I request "POST /recipes"
    Then the response status code should be 201

    View full-size slide

  55. Let’s create a POST endpoint
    use Symfony\Component\HttpFoundation\Request;
    $request = Request::createFromGlobals();
    $content = json_decode($request->getContent(), true);

    View full-size slide

  56. Let’s create a POST endpoint
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    $request = Request::createFromGlobals();
    $content = json_decode($request->getContent(), true);
    // Here we would persist the data
    $response = new Response(json_encode(array('data' => 'Hi!'), 201);
    $response->headers->set('Content-Type', 'application/json');
    $response->headers->set('Location', '/recipes/1');

    View full-size slide

  57. Shortcut: JsonResponse()
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\JsonResponse;
    $request = Request::createFromGlobals();
    $content = json_decode($request->getContent(), true);
    // Here we would persist the data
    $response = new JsonResponse(array('data' => 'Hi!'), 201);
    $response->headers->set('Location', '/recipes/1');
    Sets ContentType to application/json and encodes to JSON

    View full-size slide

  58. And what about PSR-7?

    View full-size slide

  59. Symfony PSR-7 Bridge
    HttpFoundation has helped with homogenization across frameworks
    The PSR-7 Standard has meant a step further towards standardization
    Symfony 4 will likely embrace the PSR-7 Standard

    View full-size slide

  60. Symfony PSR-7 Bridge
    HttpFoundation has helped with homogenization across frameworks
    The PSR-7 Standard has meant a step further towards standardization
    Symfony 4 will likely embrace the PSR-7 Standard
    Until then, the Symfony PSR-7 Bridge makes HttpFoundation
    objects compatible with PSR-7

    View full-size slide

  61. Symfony PSR-7 Bridge
    use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    $psr7Factory = new DiactorosFactory();
    // convert a Request
    $symfonyRequest = Request::createFromGlobals();
    $psrRequest = $psr7Factory->createRequest($symfonyRequest);
    // convert a Response
    $symfonyResponse = new Response('Content');
    $psrResponse = $psr7Factory->createResponse($symfonyResponse);
    This is how we can convert
    HttpFoundation objects to objects
    implementing HTTP message
    interfaces defined by the PSR-7

    View full-size slide

  62. Symfony PSR-7 Bridge
    use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    $psr7Factory = new DiactorosFactory();
    // convert a Request
    $symfonyRequest = Request::createFromGlobals();
    $psrRequest = $psr7Factory->createRequest($symfonyRequest);
    // convert a Response
    $symfonyResponse = new Response('Content');
    $psrResponse = $psr7Factory->createResponse($symfonyResponse);
    This is how we can convert
    HttpFoundation objects to objects
    implementing HTTP message
    interfaces defined by the PSR-7
    https://inviqa.com/blog/introduction-psr-7-symfony

    View full-size slide

  63. PSR-7 middlewares
    AccessLog
    AttributeMapper
    AuraRouter
    AuraSession
    BasePath
    BasicAuthentication
    BlockSpam
    Cache
    ClientIp
    Cors
    Csp
    Csrf
    https://github.com/oscarotero/psr7-middlewares
    DebugBar
    Delay
    DetectDevice
    DigestAuthentication
    EncodingNegotiator
    ErrorHandler
    Expires
    FastRoute
    FormTimestamp
    Firewall
    FormatNegotiator
    Geolocate
    GoogleAnalytics
    Honeypot
    Https
    ImageTransformer
    IncludeResponse
    JsonSchema
    LanguageNegotiation
    LeagueRoute
    MethodOverride
    Minify
    Payload
    PhpSession
    Piwik
    ReadResponse
    Recaptcha
    Rename
    ResponseTime
    Robots
    SaveResponse
    Shutdown
    TrailingSlash
    Uuid
    Whoops
    Www
    Following PSR-7
    Standard allows you to
    use middlewares

    View full-size slide

  64. Serializer
    Getting representations of our
    objects, back and forth
    Serializer
    HttpFoundation
    Validator
    Form
    Guard

    View full-size slide

  65. Serializer - The idea
    $recipe = new Recipe();
    $recipe->setName($content['name']);
    $recipe->setEnergy($content['energy']);
    $recipe->setServings($content['servings']);
    ...
    The request -> Our object
    (manual deserialization)

    View full-size slide

  66. Serializer - The idea
    $recipe = new Recipe();
    $recipe->setName($content['name']);
    $recipe->setEnergy($content['energy']);
    $recipe->setServings($content['servings']);
    ...
    ...
    $responseData = [
    'id' => $recipe->getId(),
    'name' => $recipe->getName(),
    'energy' => $recipe->getEnergy(),
    'servings' => $recipe->getServings(),
    ];
    $response = new JsonResponse($responseData, 201);
    The request -> Our object
    (manual deserialization)
    Our object -> The response
    (manual serialization)

    View full-size slide

  67. Serializer - The idea
    $recipe = new Recipe();
    $recipe->setName($content['name']);
    $recipe->setEnergy($content['energy']);
    $recipe->setServings($content['servings']);
    ...
    ...
    $responseData = [
    'id' => $recipe->getId(),
    'name' => $recipe->getName(),
    'energy' => $recipe->getEnergy(),
    'servings' => $recipe->getServings(),
    ];
    $response = new JsonResponse($responseData, 201);
    A lot of repetitive, boring work!
    The request -> Our object
    (manual deserialization)
    Our object -> The response
    (manual serialization)

    View full-size slide

  68. Serializing
    $responseData = [
    'id' => $recipe->getId(),
    'name' => $recipe->getName(),
    'energy' => $recipe->getEnergy(),
    'servings' => $recipe->getServings(),
    ];
    $response = new JsonResponse($responseData, 201);
    Converting our object into a JSON response

    View full-size slide

  69. Serializing
    $responseData = [
    'id' => $recipe->getId(),
    'name' => $recipe->getName(),
    'energy' => $recipe->getEnergy(),
    'servings' => $recipe->getServings(),
    ];
    $response = new JsonResponse($responseData, 201);
    --------------
    $response = new Response($serializer->serialize($recipe, 'json'), 201);
    Converting our object into a JSON response

    View full-size slide

  70. Deserializing
    $recipe = new Recipe();
    $recipe->setName($content['name']);
    $recipe->setEnergy($content['energy']);
    $recipe->setServings($content['servings']);
    Converting the JSON content request into an object

    View full-size slide

  71. Deserializing
    $recipe = new Recipe();
    $recipe->setName($content['name']);
    $recipe->setEnergy($content['energy']);
    $recipe->setServings($content['servings']);
    ------------------
    $recipe = $serializer->deserialize($content, Recipe::class, 'json');
    Converting the JSON content request into an object

    View full-size slide

  72. Serializer - The idea
    Turns objects into a specific format, and the other way around

    View full-size slide

  73. Setting up the Serializer
    use Symfony\Component\Serializer\Serializer;
    use Symfony\Component\Serializer\Encoder\XmlEncoder;
    use Symfony\Component\Serializer\Encoder\JsonEncoder;
    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
    $encoders = array(new XmlEncoder(), new JsonEncoder());
    $normalizers = array(new ObjectNormalizer());
    serializer = new Serializer($normalizers, $encoders);

    View full-size slide

  74. Setting up the Serializer
    use Symfony\Component\Serializer\Serializer;
    use Symfony\Component\Serializer\Encoder\XmlEncoder;
    use Symfony\Component\Serializer\Encoder\JsonEncoder;
    use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
    $encoders = array(new XmlEncoder(), new JsonEncoder());
    $normalizers = array(new ObjectNormalizer());
    serializer = new Serializer($normalizers, $encoders);
    http://symfony.com/doc/current/components/serializer.html

    View full-size slide

  75. Representation in API
    !=
    what we have in DB

    View full-size slide

  76. Representation in API != what we have in DB
    {
    id: 9,
    name: "victoriaq",
    password: "encryptedPassword",
    email: "[email protected]",
    avatar: "avatar.jpg",
    twitter_handler: "vicqr",
    profile: {
    id: 19,
    bio: "My bio."
    }
    }

    View full-size slide

  77. Representation in API != what we have in DB
    {
    id: 9,
    name: "victoriaq",
    password: "encryptedPassword",
    email: "[email protected]",
    avatar: "avatar.jpg",
    twitter_handler: "vicqr",
    profile: {
    id: 19,
    bio: "My bio."
    }
    }
    I may want to name this “username”

    View full-size slide

  78. Representation in API != what we have in DB
    {
    id: 9,
    name: "victoriaq",
    password: "encryptedPassword",
    email: "[email protected]",
    avatar: "avatar.jpg",
    twitter_handler: "vicqr",
    profile: {
    id: 19,
    bio: "My bio."
    }
    }
    I may want to name this “username”
    Don’t what to expose it!

    View full-size slide

  79. Representation in API != what we have in DB
    {
    id: 9,
    name: "victoriaq",
    password: "encryptedPassword",
    email: "[email protected]",
    avatar: "avatar.jpg",
    twitter_handler: "vicqr",
    profile: {
    id: 19,
    bio: "My bio."
    }
    }
    I may want to name this “username”
    Don’t what to expose it!
    Only in the profile, not in the list

    View full-size slide

  80. Representation in API != what we have in DB
    {
    id: 9,
    name: "victoriaq",
    password: "encryptedPassword",
    email: "[email protected]",
    avatar: "avatar.jpg",
    twitter_handler: "vicqr",
    profile: {
    id: 19,
    bio: "My bio."
    }
    }
    I may want to name this “username”
    Don’t what to expose it!
    We want to add “thumb_”
    Only in the profile, not in the list

    View full-size slide

  81. Representation in API != what we have in DB
    {
    id: 9,
    name: "victoriaq",
    password: "encryptedPassword",
    email: "[email protected]",
    avatar: "avatar.jpg",
    twitter_handler: "vicqr",
    profile: {
    id: 19,
    bio: "My bio."
    }
    }
    I may want to name this “username”
    Don’t what to expose it!
    Only in the profile, not in the list
    We want to add “thumb_”
    Only in version 2 of the API

    View full-size slide

  82. Representation in API != what we have in DB
    {
    id: 9,
    name: "victoriaq",
    password: "encryptedPassword",
    email: "[email protected]",
    avatar: "avatar.jpg",
    twitter_handler: "vicqr",
    profile: {
    id: 19,
    bio: "My bio."
    }
    }
    I may want to name this “username”
    Don’t what to expose it!
    Only in the profile, not in the list
    We want to add “thumb_”
    Only in version 2 of the API
    I’d like to show it as any other field

    View full-size slide

  83. Annotations
    MaxDepth
    - Detect and limit the serialization depth
    - Especially useful when serializing large trees

    View full-size slide

  84. Annotations
    MaxDepth
    - Detect and limit the serialization depth
    - Especially useful when serializing large trees
    Groups
    - Sometimes, you want to serialize different sets of attributes from your entities
    - Groups are a handy way to achieve this need

    View full-size slide

  85. Let’s create a GET endpoint
    When I request "GET /recipes"
    Then the response status code should be 200
    And only the following properties should exist:
    “”
    name
    servings
    “”

    View full-size slide

  86. Let’s create a GET endpoint
    class Recipe
    {
    /**
    * @Groups({"detail", "overview"})
    */
    public $name;
    /**
    * @Groups({"detail"})
    */
    public $energy;
    /**
    * @Groups({"detail", "overview"})
    */
    public $servings;
    }

    View full-size slide

  87. Let’s create a GET endpoint
    class Recipe
    {
    /**
    * @Groups({"detail", "overview"})
    */
    public $name;
    /**
    * @Groups({"detail"})
    */
    public $energy;
    /**
    * @Groups({"detail", "overview"})
    */
    public $servings;
    }

    View full-size slide

  88. Let’s create a GET endpoint
    $groups = ['groups' => ['overview']];
    $response = new Response($serializer->serialize($recipes, 'json', $groups), 201);
    $response->headers->set('Content-Type', 'application/json');
    return $response;
    Now we can serialize only the group overview, if we want

    View full-size slide

  89. Creating your custom normalizers
    For example, to serialize attributes with a different name

    View full-size slide

  90. Creating your custom normalizers
    use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
    class OrgPrefixNameConverter implements NameConverterInterface
    {
    public function normalize($propertyName)
    {
    return 'prefix_'.$propertyName;
    }
    public function denormalize($propertyName)
    {
    // remove prefix_ prefix
    return 'prefix_' === substr($propertyName, 0, 7) ? substr($propertyName, 7) : $propertyName;
    }
    }
    For example, to serialize attributes with a different name

    View full-size slide

  91. Validator
    Enforcing sanity
    Validator
    HttpFoundation
    Serializer
    Form
    Guard

    View full-size slide

  92. Validator - The idea
    The values sent to our DB must meet certain constraints
    We can’t leave that work to the DB itself
    We need a previous validation
    Provides tools to validate the incoming data

    View full-size slide

  93. Simplest example
    use Symfony\Component\Validator\Validation;
    use Symfony\Component\Validator\Constraints\Length;
    use Symfony\Component\Validator\Constraints\NotBlank;
    $validator = Validation::createValidator();
    $violations = $validator->validate('Spanish omelette', array(
    new Length(array('min' => 2))
    ));

    View full-size slide

  94. Simplest example
    use Symfony\Component\Validator\Validation;
    use Symfony\Component\Validator\Constraints\Length;
    use Symfony\Component\Validator\Constraints\NotBlank;
    $validator = Validation::createValidator();
    $violations = $validator->validate('Spanish omelette', array(
    new Length(array('min' => 2))
    ));
    Constraints...
    (rule formulation)

    View full-size slide

  95. Simplest example
    use Symfony\Component\Validator\Validation;
    use Symfony\Component\Validator\Constraints\Length;
    use Symfony\Component\Validator\Constraints\NotBlank;
    $validator = Validation::createValidator();
    $violations = $validator->validate('Spanish omelette', array(
    new Length(array('min' => 2))
    ));
    and validators
    (logic there)

    View full-size slide

  96. Simplest example
    use Symfony\Component\Validator\Validation;
    use Symfony\Component\Validator\Constraints\Length;
    use Symfony\Component\Validator\Constraints\NotBlank;
    $validator = Validation::createValidator();
    $violations = $validator->validate('Spanish omelette', array(
    new Length(array('min' => 2))
    ));
    What happens with more complex validations…
    such as validating an object?

    View full-size slide

  97. Validating objects
    /**
    * @ORM\Column(type="integer")
    * @Assert\NotBlank()
    * @Assert\GreaterThanOrEqual(
    * value=0,
    * message="This universe is not so lucky. This value must be at least zero"
    * )
    **/
    private $energy;
    /**
    * @ORM\Column(type="integer")
    * @Assert\NotBlank()
    **/
    private $servings;
    Validator needs to know
    which constraints apply to
    the object properties

    View full-size slide

  98. Validating objects
    /**
    * @ORM\Column(type="integer")
    * @Assert\NotBlank()
    * @Assert\GreaterThanOrEqual(
    * value=0,
    * message="This universe is not so lucky. This value must be at least zero"
    * )
    **/
    private $energy;
    /**
    * @ORM\Column(type="integer")
    * @Assert\NotBlank()
    **/
    private $servings;
    Annotations are handy, but it
    can be done with yml, xml,
    explicit PHP...

    View full-size slide

  99. Constraints
    https://symfony.com/doc/current/reference/constraints.html
    You have about 50 constraints defined in the Validator component
    (from NotNull to ISBN…)

    View full-size slide

  100. Constraints
    https://symfony.com/doc/current/reference/constraints.html
    You have about 50 constraints defined in the Validator component
    (from NotNull to ISBN…)
    And you can create your own

    View full-size slide

  101. Returning errors in our API
    if (0 !== count($violations)) {
    $errors = [];
    foreach ($violations as $violation) {
    $errors[$violation->getPropertyPath()] = $violation->getMessage();
    }
    $response = new JsonResponse($errors, 400);
    $response->send();
    return;
    }
    Good validation and error handling are key in an API

    View full-size slide

  102. Form
    Powering up validation and
    deserialization
    Form
    HttpFoundation
    Serializer
    Validator
    Guard

    View full-size slide

  103. Form - The idea
    Not much difference between handling an HTML form and API data
    Equivalent to deserializing + validating
    A powerful deserializer
    We can reuse work done in HTML forms
    Provides powerful validation plus serialization

    View full-size slide

  104. Form - The idea
    Not much difference between handling an HTML form and API data
    Equivalent to deserializing + validating
    A powerful deserializer
    We can reuse work done in HTML forms
    https://knpuniversity.com/screencast/symfony-rest/form-post

    View full-size slide

  105. Let’s create a PUT endpoint
    When I request "PUT /recipes/1"
    Then the response status code should be 200

    View full-size slide

  106. Let’s create a PUT endpoint
    $formFactory = Forms::createFormFactoryBuilder()
    ->addExtension(new ValidatorExtension($validator))
    ->getFormFactory();
    $data = json_decode($request->getContent(), true);
    $recipe = $this->getDoctrine()->getRepository('AppBundle:Recipe')->find($recipeId);
    $form = $formFactory->create(RecipeType::class, $recipe);
    $form->submit($data);
    if (!$form->isValid()) {
    $response = new Response($serializer->serialize($form, 'json'), 400);
    $response->headers->set('Content-Type', 'application/json');
    return $response;
    }

    View full-size slide

  107. Let’s create a PUT endpoint
    $formFactory = Forms::createFormFactoryBuilder()
    ->addExtension(new ValidatorExtension($validator))
    ->getFormFactory();
    $data = json_decode($request->getContent(), true);
    $recipe = $this->getDoctrine()->getRepository('AppBundle:Recipe')->find($recipeId);
    $form = $formFactory->create(RecipeType::class, $recipe);
    $form->submit($data);
    if (!$form->isValid()) {
    $response = new Response($serializer->serialize($form, 'json'), 400);
    $response->headers->set('Content-Type', 'application/json');
    return $response;
    }

    View full-size slide

  108. Let’s create a PUT endpoint
    $formFactory = Forms::createFormFactoryBuilder()
    ->addExtension(new ValidatorExtension($validator))
    ->getFormFactory();
    $data = json_decode($request->getContent(), true);
    $recipe = $this->getDoctrine()->getRepository('AppBundle:Recipe')->find($recipeId);
    $form = $formFactory->create(RecipeType::class, $recipe);
    $form->submit($data);
    if (!$form->isValid()) {
    $response = new Response($serializer->serialize($form, 'json'), 400);
    $response->headers->set('Content-Type', 'application/json');
    return $response;
    }

    View full-size slide

  109. Let’s create a PUT endpoint
    $formFactory = Forms::createFormFactoryBuilder()
    ->addExtension(new ValidatorExtension($validator))
    ->getFormFactory();
    $data = json_decode($request->getContent(), true);
    $recipe = $this->getDoctrine()->getRepository('AppBundle:Recipe')->find($recipeId);
    $form = $formFactory->create(RecipeType::class, $recipe);
    $form->submit($data);
    if (!$form->isValid()) {
    $response = new Response($serializer->serialize($form, 'json'), 400);
    $response->headers->set('Content-Type', 'application/json');
    return $response;
    }

    View full-size slide

  110. Let’s create a PUT endpoint
    $formFactory = Forms::createFormFactoryBuilder()
    ->addExtension(new ValidatorExtension($validator))
    ->getFormFactory();
    $data = json_decode($request->getContent(), true);
    $recipe = $this->getDoctrine()->getRepository('AppBundle:Recipe')->find($recipeId);
    $form = $formFactory->create(RecipeType::class, $recipe);
    $form->submit($data);
    if (!$form->isValid()) {
    $response = new Response($serializer->serialize($form, 'json'), 400);
    $response->headers->set('Content-Type', 'application/json');
    return $response;
    }

    View full-size slide

  111. Let’s create a PUT endpoint
    $formFactory = Forms::createFormFactoryBuilder()
    ->addExtension(new ValidatorExtension($validator))
    ->getFormFactory();
    $data = json_decode($request->getContent(), true);
    $recipe = $this->getDoctrine()->getRepository('AppBundle:Recipe')->find($recipeId);
    $form = $formFactory->create(RecipeType::class, $recipe);
    $form->submit($data);
    if (!$form->isValid()) {
    $response = new Response($serializer->serialize($form, 'json'), 400);
    $response->headers->set('Content-Type', 'application/json');
    return $response;
    }
    https://github.com/VictoriaQ/rest-symfony-components/commit/bd2e044d6a5269e4e415d8fadcf7710a9ede27de

    View full-size slide

  112. POST to create and PUT
    to update?

    View full-size slide

  113. On PUT and POST
    Common knowledge:
    - POST to create resources
    - PUT to update resources

    View full-size slide

  114. On PUT and POST
    Common knowledge:
    - POST to create resources
    - PUT to update resources
    Not true

    View full-size slide

  115. On PUT and POST
    Common knowledge:
    - POST to create resources
    - PUT to update resources
    Not true
    (but somehow true)

    View full-size slide

  116. On PUT and POST
    Common knowledge:
    - POST to create resources
    - PUT to update resources
    Not true
    (but somehow true)
    (some people get very angry with this)

    View full-size slide

  117. On PUT and POST
    PUT if:
    - The operation is idempotent
    - URI = address of the resource
    BOTH need to be true. Otherwise, POST.
    This is the whole truth

    View full-size slide

  118. And do I have to return
    the resource?

    View full-size slide

  119. Do I have to return the resource?
    Many say that we do not
    Some clients assume that we will

    View full-size slide

  120. Do I have to return the resource?
    Many say that we do not
    Some clients assume that we will
    As always, be consistent with your choices

    View full-size slide

  121. Guard
    Authenticating
    Guard
    HttpFoundation
    Serializer
    Validator
    Form

    View full-size slide

  122. Guard - The idea
    Security Component allows to implement authentication
    Very powerful and flexible… But complex too
    Simplifies the authentication provided by the Security Component

    View full-size slide

  123. Just about one class with seven methods
    We only need to implement GuardAuthenticationInterface

    View full-size slide

  124. Just about one class with seven methods
    We only need to implement GuardAuthenticationInterface
    With its seven methods:
    class TokenAuthenticator extends AbstractGuardAuthenticator implements Guard\GuardAuthenticatorInterface {
    public function getCredentials(Request $request)
    public function getUser($credentials, UserProviderInterface $userProvider)
    public function checkCredentials($credentials, UserInterface $user)
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    public function supportsRememberMe()
    public function start(Request $request, AuthenticationException $authException = null)
    }
    http://symfony.com/blog/new-in-symfony-2-8-guard-authentication-component

    View full-size slide

  125. Just about one class with seven methods
    We only need to implement GuardAuthenticationInterface
    With its seven methods:
    class TokenAuthenticator extends AbstractGuardAuthenticator implements Guard\GuardAuthenticatorInterface {
    public function getCredentials(Request $request)
    public function getUser($credentials, UserProviderInterface $userProvider)
    public function checkCredentials($credentials, UserInterface $user)
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    public function supportsRememberMe()
    public function start(Request $request, AuthenticationException $authException = null)
    }
    http://symfony.com/blog/new-in-symfony-2-8-guard-authentication-component

    View full-size slide

  126. But at this point
    You need EventDispatcher, HttpKernel, Routing...

    View full-size slide

  127. But at this point
    You need EventDispatcher, HttpKernel, Routing...
    Starts looking like a good idea to use a complete framework

    View full-size slide

  128. III. USING THE FULL FRAMEWORK

    View full-size slide

  129. Uses all these Components
    http://symfony.com/projects/symfonyfs (actually a few more)

    View full-size slide

  130. Components and bundles
    - Component -> decoupled and reusable library
    - Bundle -> tied to the Symfony Framework

    View full-size slide

  131. Components and bundles
    - Component -> decoupled and reusable library
    - Bundle -> tied to the Symfony Framework
    Often you have:
    Functionality
    Library
    Integration with SF
    Configuration
    Dependency injection
    Bundle
    +

    View full-size slide

  132. Components and bundles
    - Component -> decoupled and reusable library
    - Bundle -> tied to the Symfony Framework
    Often you have:
    Functionality
    Library
    Integration with SF
    Configuration
    Dependency injection
    Bundle
    +
    Let’s see a few Bundles that can be useful for our API

    View full-size slide

  133. JMSSerializerBundle

    View full-size slide

  134. JMSSerializerBundle
    Another possibility for serialization / deserialization
    - JMSSerializer has lots of useful annotations
    - Perhaps easier to setup and start using than Sf Serializer
    - Sf Serializer is more about writing your own normalizers

    View full-size slide

  135. JMSSerializerBundle
    Some cool features:
    - Three exclusion strategies (Exclude, Groups, Versions)
    - Configurable properties (Virtual Props., Accessors)
    - Events provide extra flexibility
    - XML highly configurable
    Good alternative to Serializer, up to you

    View full-size slide

  136. FOSRestBundle

    View full-size slide

  137. FOSRestBundle
    Set of tools:
    - View layer that deals with the different representations
    - Body request decoder
    - Listener that performs content negotiation
    - Parameter validator and converter
    - ALLOW header manager
    - Exception decoder
    - Unified REST routing system

    View full-size slide

  138. FOSRestBundle
    Set of tools:
    - View layer that deals with the different representations
    - Body request decoder
    - Listener that performs content negotiation
    - Parameter validator and converter
    - ALLOW header manager
    - Exception decoder
    - Unified REST routing system
    You can live without it, but it is quite useful when you have it

    View full-size slide

  139. LexikJWTAuthenticationBundle

    View full-size slide

  140. LexikJWTAuthenticationBundle
    JSON Web Token (JWT) is a compact URL-safe means of
    representing claims to be transferred between two
    parties. The claims in a JWT are encoded as a JSON
    object that is digitally signed using JSON Web Signature
    (JWS).

    View full-size slide

  141. LexikJWTAuthenticationBundle
    JSON Web Token (JWT) is a compact URL-safe means of
    representing claims to be transferred between two
    parties. The claims in a JWT are encoded as a JSON
    object that is digitally signed using JSON Web Signature
    (JWS).
    Two built-in token encoders, based on:
    - namshi/jose
    - lcobucci/jwt

    View full-size slide

  142. LexikJWTAuthenticationBundle
    POST /login_check HTTP/1.1
    Host: localhost:8000
    Content-Type: application/json
    Accept: application/json
    Cache-Control: no-cache
    {"_username": "user",
    "_password": "userpass"}
    endpoint configured
    in security.yml
    credentials

    View full-size slide

  143. LexikJWTAuthenticationBundle
    {"token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJleHAiOjE0MzIzODc2ODcsInVzZXJuYW1l
    IjoidXNlciIsImlhdCI6IjE0MzIzMDEyODcifQ.QgtJ-C96gMDzl1syuRGdpIb02sGnC-oKyLFqiNMX9Lny
    hYV__R7itxLPEepOqzkhOiL-O7EdFEdqFjK9vRgk67MRgfKCh5Yuir9aUkVYeZXpyVOGUgPGDpHtFi74_G9
    EY4FGXzt7zhHoP_Y-6GwFSc_7HTeW7sm3ifGpYSlifyx3jmggkHoTc-1_kxXFdceoSCOoPf-uDaaQtLOwKF
    qUJCYaAcGXaS0FI_GDnIyX4bDCcPi3_6AvLLuSmfCaZqRofPvasANfhxRmE6iZ9wZ9dcKX1siPu7F_lFpJe
    3UgMy6hr3kDYPz5H4gOcu0A3JWV7lCIpThF8j-G1eN7PCIeLCQBpq08rkM9SrohWUiMcuqCQYjWIbB-jF0Z
    Uv762Cvv2Y-
    e9gqXsCYmg9NrLURLNJdce_Yl3QyWECYSKwaFkz8p8VTqrKtxrM0Gd-qNZ_uT4Hq8-BZ7ZDGCfQ0Dm4qC2P
    qXtPFfvmi_RQ9S2xmx93At0gBtLRrQPSqxv7ZfqtDjAKsCgOsMPU1yS-4h8s3Wxb4_flwvBZJbnHgkmFFR8
    -p_a8FwAHXoHXkPokTX-hZy-UjfV1x8N2F8u0_ndsHS0cZdFhX9grpBsRoR058YFTqFgAC-6s3d6AeAX7WX
    VIKu-
    x1qjcsrkNND2jxoEppZphT6ecC0JgT6ioGFuHYZWbA"}

    View full-size slide

  144. LexikJWTAuthenticationBundle
    GET /recipes/1 HTTP/1.1
    Host: localhost:8000
    Content-Type: application/json
    Accept: application/json
    Authorization: Bearer
    eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJleHAiOjE0MzIzNzk0MjQsInVzZXJuYW1lIjoidXNlci
    IsImlhdCI6IjE0MzIyOTMwMjQifQ.Wb8NlbNNvCA5V7QxKoN_uYLmCRiqjt0ES5gIlvpyrBmVKMAXQ_cj6M
    NqbXcHp28NXNGJidcV2qJ1-8zWGkbHBDMZCAOp_JX74EXdeQYa0c2bV7HzKyUCEX8bOii25Jmt8YpWUa5og
    w58rWVhE-2CRn0fQS_gHn7d3Ke1w4NPjaLgnhSthZKfR2Ytd9U6A_3w6X2fS2R24DWWmb8KThMtSwXOtGVq
    EQVEnsro91RPHARg-na1ww_BnE-3Xas_427g84bsUHNIIlDs7BYAEF323h3AZrPB4Tx1tNabEWIsBJzEAKE
    LBDoCrqmspK0AFrGx-RIjiRMpMzDlTHMjt6hML7SO1y3AJbrD5AsxNLITtnaxfkFYl3n0XIH8Gd2ZRdFDsj
    XGPPZ6VLFHfUIe1BoPmLtFt5CK4PeDb-_BsbdA3tGEkVgfTrh9bSMY9mXZ-KCg8F8cbK5A-CqURqRFpTts1
    OBRUcOR1c6GtrPHpGsuoPGE90mTiOJPyajUK8lgdv1yBMt7WSEJNRXMLxetk57l53FLiLVObfV7D-LGC9R1
    8gIedzxmTXXxteV83izNuzQChAiRBU73W-5kjGkmpvr05Q_rd33dTn9wxEe0I0nnEK_MeSvqz2nH23xj7RU
    wGH
    qsrWlfVZZqZcVP59njex0LWWgb7PGKmJwL8ze2MrBft-U
    Provides a very simple way of authenticating

    View full-size slide

  145. BazingaHateoasBundle

    View full-size slide

  146. BazingaHateoasBundle
    Allows you to reach REST level 3
    Hypermedia As The Engine Of Application State

    View full-size slide

  147. BazingaHateoasBundle
    GET /recipes/1 HTTP/1.1
    Host: localhost:8000
    Content-Type: application/json
    Accept: application/json
    {
    "id":1,
    "name":"Spanish omelette",
    "energy":"500",
    "servings":4
    }

    View full-size slide

  148. BazingaHateoasBundle
    GET /recipes/1 HTTP/1.1
    Host: localhost:8000
    Content-Type: application/json
    Accept: application/json
    {
    "id":1,
    "name":"Spanish omelette",
    "energy":"500",
    "servings":4,
    "_links": {
    "self": {
    "href": "\/recipes\/1"
    }
    }
    }
    Level 3! :-)

    View full-size slide

  149. BazingaHateoasBundle
    use Doctrine\ORM\Mapping as ORM;
    use Hateoas\Configuration\Annotation as Hateoas;
    use JMS\Serializer\Annotation as Serializer;
    /**
    * Recipe
    *
    * @ORM\Table()
    * @ORM\Entity(repositoryClass="AppBundle\Entity\RecipeRepository")
    * @Hateoas\Relation("self",
    * href = @Hateoas\Route(
    * "fosrest_api_get_recipe",
    * parameters = { "id" = "expr(object.getId())" }))
    */
    class Recipe
    {

    }

    View full-size slide

  150. BazingaHateoasBundle
    use Doctrine\ORM\Mapping as ORM;
    use Hateoas\Configuration\Annotation as Hateoas;
    use JMS\Serializer\Annotation as Serializer;
    /**
    * Recipe
    *
    * @ORM\Table()
    * @ORM\Entity(repositoryClass="AppBundle\Entity\RecipeRepository")
    * @Hateoas\Relation("self",
    * href = @Hateoas\Route(
    * "fosrest_api_get_recipe",
    * parameters = { "id" = "expr(object.getId())" }))
    */
    class Recipe
    {

    }
    Potentially, allows you to reach level 3

    View full-size slide

  151. And do I actually HAVE
    TO reach level 3?

    View full-size slide

  152. Do I have to reach level 3 of REST?
    “If the engine of application state (and hence the
    API) is not being driven by hypertext, then it
    cannot be RESTful and cannot be a REST API.
    Period.”
    Roy T. Fielding (author of REST thesis and REST concept itself)
    http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
    Theoretically:

    View full-size slide

  153. Do I have to reach level 3 of REST?
    “If the engine of application state (and hence the
    API) is not being driven by hypertext, then it
    cannot be RESTful and cannot be a REST API.
    Period.”
    Theoretically: In the Real World:
    Roy T. Fielding (author of REST thesis and REST concept itself)
    http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

    View full-size slide

  154. Do I have to reach level 3 of REST?
    “If the engine of application state (and hence the
    API) is not being driven by hypertext, then it
    cannot be RESTful and cannot be a REST API.
    Period.”
    Theoretically: In the Real World:
    It is up to you, you have to evaluate
    Roy T. Fielding (author of REST thesis and REST concept itself)
    http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

    View full-size slide

  155. NelmioAPIDocBundle

    View full-size slide

  156. NelmioAPIDocBundle
    Documentation bundle
    You create documentation while coding
    Uses code introspection a lot

    View full-size slide

  157. NelmioAPIDocBundle
    Documentation bundle
    You create documentation while coding
    Uses code introspection a lot
    Easy to keep the documentation updated

    View full-size slide

  158. NelmioAPIDocBundle
    /**
    * Gets a Recipe
    * @ApiDoc(
    * resource=true,
    * section="recipe",
    * deprecated="true",
    * tags={"hi", "anothertag" = "#34a523"},
    * views = { "default", "v2" },
    * statusCodes = {
    * 200 = "Returned when successful",
    * 404 = "Returned when the recipe is not found"
    * })
    * @QueryParam(name="page", requirements="\d+", default="1", description="Page of the overview.")
    * @param ParamFetcher $paramFetcher
    * @return array
    */
    public function getPlatosAction(ParamFetcher $paramFetcher)
    {

    View full-size slide

  159. NelmioAPIDocBundle

    View full-size slide

  160. NelmioAPIDocBundle

    View full-size slide

  161. Testing, some tips
    Invest some time in setting up a nice testing environment

    View full-size slide

  162. Testing, some tips
    Postman, easy to set-up
    Invest some time in setting up a nice testing environment

    View full-size slide

  163. Testing, some tips
    Postman, easy to set-up
    Guzzle + PHPUnit, your bread and butter
    Invest some time in setting up a nice testing environment

    View full-size slide

  164. Guzzle
    $data = array(
    'name' => 'Spanish omelette',
    'energy' => 500,
    'servings' => 4
    );
    $request = $client->post('/recipes', null, json_encode($data));
    $response = $request->send();
    echo $response;

    View full-size slide

  165. Testing, some tips
    Postman, easy to set-up
    Guzzle + PHPUnit, your bread and butter
    Very important to handle errors properly
    Invest some time in setting up a nice testing environment

    View full-size slide

  166. Error handling - Nested errors
    Create test “errors.children.servings.errors should exist”
    {
    "code": 400,
    "message": "Validation Failed",
    "errors": {
    "children": {
    "name": [ ],
    "energy": [ ],
    "servings": {
    "errors": [
    "This value should be greater than or equal to 0."
    ]
    }
    }
    }

    View full-size slide

  167. Error handling - HTML errors
    Not the type of error that we want to receive in our API

    View full-size slide

  168. Error handling - HTML errors
    Not the type of error that we want to receive in our API





    No route found for "GET /recipes" (404 Not Found)









    src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALYAAAA+CAMAAACxzRGDAAAAUVBMVEX////Ly8yko6WLioxkYmVXVVkwLjLl5eWxsLJKSEzy8vJxcHLY
    2Ni+vb89Oz9XVVh+fH+Yl5n///+xsbLY2Nlxb3KkpKWXlph+fX+LiYy+vr/IZP61AAAAAXRSTlMAQObYZgAABRBJREFUeNrVmtuWoyAQRS1FEEQSzQU7//+hYxUiXsKQZLJW
    M+chsUloN+WhCuguYoKyYqzmvGasKqH4HyRKxndipcgcumH8qViTM7TkUclcwaHmf5XM0eWq4km1KjdqXfMXJHVe1J3hL8lk5fCGv6wmT+o0d87U+XNrk0Y9nfv+7LM6
    ZJH5ZBL6LAbSxQ3Q5FDr22Skr8PQSy4n7isnsQxSX4r6pobhjCHHeDNOKrO3yGmCvZOjV9jmt8ulTdXFKdbKLNh+kOMvBzuVRa4Y7MUsdEUSWQe7xxCfZmcwjHU83LqzFv
    SbJQOXQvptbPnEFoyZtUUGwTeKuLuTHyT1kaP0P6cR01OKvv448gtl61dqZfmJezQmU/t+1R2fJLtBwXV6uWGwB9SZPrn0fKO2WAvQN1PUhHjTom3xgXYTkvlSKHs19Ohsl
    ETq6X3HrXbjt8XbGj9b4Gi+lUAnL6XxQj8Pyk9N4Bt1xUrsLVN/3isYMug8rODMdbgOvoHs8uAb2fcANIAzkKCLYy+AXRpSU8sr1r4P67xhLgPp7vM32zlqt7Bhq2fI1Hwp+VgA
    Nxok59SsGV3oqdUL0YVDMRY7Yg8QLbVUU4NZNoOq5hJHuxEM28Sh/IyUZ8D3reR+yc58EGvOy2U0HQL6G9V+kWyEWHmzaMx6t4o9RhOm/riUiYrzqij4Ptqkn7AaCXqc+F
    47m04ahfde7YIz8RHEBN6BdV

    View full-size slide

  169. Error handling - HTML errors
    We can use the Symfony Crawler to return something nicer





    No route found for "GET /recipes" (404 Not Found)









    src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALYAAAA+CAMAAACxzRGDAAAAUVBMVEX////Ly8yko6WLioxkYmVXVVkwLjLl5eWxsLJKSEzy8vJxcHLY
    2Ni+vb89Oz9XVVh+fH+Yl5n///+xsbLY2Nlxb3KkpKWXlph+fX+LiYy+vr/IZP61AAAAAXRSTlMAQObYZgAABRBJREFUeNrVmtuWoyAQRS1FEEQSzQU7//+hYxUiXsKQZLJW
    M+chsUloN+WhCuguYoKyYqzmvGasKqH4HyRKxndipcgcumH8qViTM7TkUclcwaHmf5XM0eWq4km1KjdqXfMXJHVe1J3hL8lk5fCGv6wmT+o0d87U+XNrk0Y9nfv+7LM6
    ZJH5ZBL6LAbSxQ3Q5FDr22Skr8PQSy4n7isnsQxSX4r6pobhjCHHeDNOKrO3yGmCvZOjV9jmt8ulTdXFKdbKLNh+kOMvBzuVRa4Y7MUsdEUSWQe7xxCfZmcwjHU83LqzFv
    SbJQOXQvptbPnEFoyZtUUGwTeKuLuTHyT1kaP0P6cR01OKvv448gtl61dqZfmJezQmU/t+1R2fJLtBwXV6uWGwB9SZPrn0fKO2WAvQN1PUhHjTom3xgXYTkvlSKHs19Ohsl
    ETq6X3HrXbjt8XbGj9b4Gi+lUAnL6XxQj8Pyk9N4Bt1xUrsLVN/3isYMug8rODMdbgOvoHs8uAb2fcANIAzkKCLYy+AXRpSU8sr1r4P67xhLgPp7vM32zlqt7Bhq2fI1Hwp+VgA
    Nxok59SsGV3oqdUL0YVDMRY7Yg8QLbVUU4NZNoOq5hJHuxEM28Sh/IyUZ8D3reR+yc58EGvOy2U0HQL6G9V+kWyEWHmzaMx6t4o9RhOm/riUiYrzqij4Ptqkn7AaCXqc+F
    47m04ahfde7YIz8RHEBN6BdV
    This is what we want

    View full-size slide

  170. Testing, some tips
    Postman, easy to set-up
    Guzzle + PHPUnit, your bread and butter
    Very important to handle errors properly
    Isolated DB for testing (can use SQlite)
    Invest some time in setting up a nice testing environment

    View full-size slide

  171. Testing, some tips
    Postman, easy to set-up
    Guzzle + PHPUnit, your bread and butter
    Very important to handle errors properly
    Isolated DB for testing (can use SQlite)
    Set up proper fixtures (Alice & Faker are nice tools)
    Invest some time in setting up a nice testing environment

    View full-size slide

  172. Fixtures with Alice & Faker
    AppBundle\Entity\Recipe:
    recipe1:
    name: Spanish omelette
    energy: 500
    servings: 4
    recipe{2..20}:
    name:
    energy:
    servings:

    View full-size slide

  173. Fixtures with Alice & Faker
    AppBundle\Entity\Recipe:
    recipe1:
    name: Spanish omelette
    energy: 500
    servings: 4
    recipe{2..20}:
    name:
    energy:
    servings:
    Known values, that
    we may want to use
    in some test

    View full-size slide

  174. Fixtures with Alice & Faker
    AppBundle\Entity\Recipe:
    recipe1:
    name: Spanish omelette
    energy: 500
    servings: 4
    recipe{2..20}:
    name:
    energy:
    servings:
    https://github.com/hautelook/AliceBundle
    https://github.com/fzaninotto/Faker
    Known values, that
    we may want to use
    in some test
    Values created with
    generators
    Many generators available:

    View full-size slide

  175. V. USES FOR YOUR API

    View full-size slide

  176. Uses for an API
    Api serves content to Mobile/Rich web apps/…

    View full-size slide

  177. Uses for an API
    Api serves content to Mobile/Rich web apps/…
    Abstract parts of a monolith into isolated services

    View full-size slide

  178. Uses for an API
    Api serves content to Mobile/Rich web apps/…
    Abstract parts of a monolith into isolated services
    Undertake a migration

    View full-size slide

  179. Migrations - Option 1

    View full-size slide

  180. Migrations - Option 1
    We replace functionalities, one route at a time

    View full-size slide

  181. Migrations - Option 1
    We replace functionalities, one route at a time

    View full-size slide

  182. Migrations - Option 1

    View full-size slide

  183. Migrations - Option 1
    https://speakerdeck.com/hhamon/bringing-symfony-components-into-your-legacy-code

    View full-size slide

  184. Migrations - Option 1
    https://speakerdeck.com/hhamon/bringing-symfony-components-into-your-legacy-code
    Good idea when the team
    feels very comfortable
    with the new framework

    View full-size slide

  185. Migrations - Option 2

    View full-size slide

  186. Migrations - Option 2
    Old system replace its code with calls to new Symfony API

    View full-size slide

  187. Migrations - Option 2
    Good idea when team knows legacy code very well

    View full-size slide

  188. Migrations - Option 2
    Good idea when team knows legacy code very well

    View full-size slide

  189. VI. FINAL THOUGHTS

    View full-size slide

  190. Summarizing
    Whatever the choices you make building your API:
    - Be consistent
    - Take profit of using a common language
    REST and Symfony Components make that easy
    - They fit well together, both built around HTTP
    - A few Components provide a lot
    - You can consider using the full framework if you want more
    Make sure that you set up a proper test environment

    View full-size slide

  191. https://github.com/VictoriaQ/sonatademo
    @vicqr
    [email protected]
    Training, consulting and
    development
    https://github.com/VictoriaQ/rest-symfony-components
    @vicqr
    [email protected]
    Thanks!

    View full-size slide