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

  3. I. INTRODUCTION

    View Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View 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 Slide

  10. What you already know about REST

    View Slide

  11. Sane way to approach REST

    View Slide

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

    View Slide

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

    View Slide

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

    View 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 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 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 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 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 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 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 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 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 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 Slide

  25. View Slide

  26. View Slide

  27. Why Symfony & REST?

    View Slide

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

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

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

    View Slide

  31. Why Symfony
    Components?

    View Slide

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

    View Slide

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

    View Slide

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

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

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

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

    View Slide

  47. II. SYMFONY COMPONENTS

    View Slide

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

    View Slide

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

    View Slide

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

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

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

    View Slide

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

    View Slide

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

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

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

    View Slide

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

    View Slide

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

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

  60. And what about PSR-7?

    View Slide

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

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

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

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

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

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

    View Slide

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

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

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

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

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

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

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

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

    View Slide

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

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

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

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

    View 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”

    View 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!

    View 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

    View 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!
    We want to add “thumb_”
    Only in the profile, not in the list

    View Slide

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

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

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

    View Slide

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

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

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

    View Slide

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

    View Slide

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

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

    View Slide

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

  93. Validator
    Enforcing sanity
    Validator
    HttpFoundation
    Serializer
    Form
    Guard

    View Slide

  94. 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 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))
    ));

    View 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))
    ));
    Constraints...
    (rule formulation)

    View Slide

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

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

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

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

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

    View Slide

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

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

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

    View Slide

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

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

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

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

    View Slide

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

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

  114. POST to create and PUT
    to update?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

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

  120. And do I have to return
    the resource?

    View Slide

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

    View Slide

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

  123. Guard
    Authenticating
    Guard
    HttpFoundation
    Serializer
    Validator
    Form

    View Slide

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

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

    View Slide

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

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

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

    View Slide

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

    View Slide

  130. III. USING THE FULL FRAMEWORK

    View Slide

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

    View Slide

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

    View Slide

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

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

  135. JMSSerializerBundle

    View Slide

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

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

  138. FOSRestBundle

    View Slide

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

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

  141. LexikJWTAuthenticationBundle

    View Slide

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

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

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

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

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

  147. BazingaHateoasBundle

    View Slide

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

    View Slide

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

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

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

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

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

    View 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.”
    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 Slide

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

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

  157. NelmioAPIDocBundle

    View Slide

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

    View Slide

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

    View Slide

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

  161. NelmioAPIDocBundle

    View Slide

  162. NelmioAPIDocBundle

    View Slide

  163. IV. TESTING

    View Slide

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

    View Slide

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

    View Slide

  166. Postman

    View Slide

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

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

    View Slide

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

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

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

    View Slide

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

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

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

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

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

    View Slide

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

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

  179. V. USES FOR YOUR API

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  183. Migrations - Option 1

    View Slide

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

    View Slide

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

    View Slide

  186. Migrations - Option 1

    View Slide

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

    View Slide

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

  189. Migrations - Option 2

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  193. VI. FINAL THOUGHTS

    View Slide

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

  195. View Slide

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

    View Slide