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 dig into those components, showing how to successfully deal with problems such as adapting our models to the desired representations back and forth, how to validate the incoming data, or how to implement our authentication strategies. Furthermore, we will see which additional tools do we have when using the full framework. All of it from a practical approach based upon real use cases.

281604c1a5357a164f2a9cd6e403b4e3?s=128

Victoria Quirante

May 30, 2017
Tweet

Transcript

  1. Building RESTful APIs with Symfony Components Victoria Quirante

  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 victoria@limenius.com
  3. I. INTRODUCTION

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

  5. Victoria Quirante @vicqr victoria@limenius.com Possible reasons AGAINST Perhaps I can

    think of a better way to structure an API
  6. Victoria Quirante @vicqr victoria@limenius.com Possible reasons AGAINST Perhaps I can

    think of a better way to structure an API REST seems very controversial and confusing in some points
  7. Victoria Quirante @vicqr victoria@limenius.com Main reasons to go for REST

    REST makes the most of HTTP
  8. Victoria Quirante @vicqr victoria@limenius.com Main reasons to go for REST

    REST makes the most of HTTP It means to have a common language
  9. Victoria Quirante @vicqr victoria@limenius.com Main reasons to go for REST

    REST makes the most of HTTP It means to have a common language These are very powerful reasons
  10. Victoria Quirante @vicqr victoria@limenius.com What you already know about REST

  11. Victoria Quirante @vicqr victoria@limenius.com Sane way to approach REST

  12. Victoria Quirante @vicqr victoria@limenius.com Sane way to approach REST 1.

    Learn the stuff that is commonly accepted
  13. Victoria Quirante @vicqr victoria@limenius.com Sane way to approach REST 1.

    Learn the stuff that is commonly accepted Use nouns for the resources!
  14. Victoria Quirante @vicqr victoria@limenius.com Sane way to approach REST 1.

    Learn the stuff that is commonly accepted Use nouns for the resources! Use HTTP verbs!
  15. Victoria Quirante @vicqr victoria@limenius.com 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!
  16. Victoria Quirante @vicqr victoria@limenius.com Sane way to approach REST 1.

    Learn the stuff that is commonly accepted 2. Be aware of the grey areas / controversial points
  17. Victoria Quirante @vicqr victoria@limenius.com 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?
  18. Victoria Quirante @vicqr victoria@limenius.com 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?
  19. Victoria Quirante @vicqr victoria@limenius.com 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?
  20. Victoria Quirante @vicqr victoria@limenius.com 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?
  21. Victoria Quirante @vicqr victoria@limenius.com 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?
  22. Victoria Quirante @vicqr victoria@limenius.com 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…
  23. Victoria Quirante @vicqr victoria@limenius.com 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
  24. Victoria Quirante @vicqr victoria@limenius.com 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
  25. None
  26. None
  27. Why Symfony & REST?

  28. Victoria Quirante @vicqr victoria@limenius.com 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
  29. Victoria Quirante @vicqr victoria@limenius.com 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
  30. Victoria Quirante @vicqr victoria@limenius.com Why Symfony & REST? REST ♥

    HTTP Symfony ♥ HTTP
  31. Why Symfony Components?

  32. Victoria Quirante @vicqr victoria@limenius.com Symfony is two things 1. A

    full stack framework
  33. Victoria Quirante @vicqr victoria@limenius.com Symfony is two things 1. A

    full stack framework 2. A set of independent components
  34. Victoria Quirante @vicqr victoria@limenius.com 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
  35. Components used in popular projects http://symfony.com/projects

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

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

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

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

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

  41. Victoria Quirante @vicqr victoria@limenius.com Components that we are going to

    see in detail HttpFoundation Serializer Validator Form Guard
  42. Victoria Quirante @vicqr victoria@limenius.com Where/when can you apply this knowledge

    - Working with the full Symfony framework
  43. Victoria Quirante @vicqr victoria@limenius.com Where/when can you apply this knowledge

    - Working with the full Symfony framework - Using some of these components in some other framework
  44. Victoria Quirante @vicqr victoria@limenius.com 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
  45. Victoria Quirante @vicqr victoria@limenius.com 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
  46. Victoria Quirante @vicqr victoria@limenius.com See repository https://github.com/VictoriaQ/rest-symfony-components

  47. II. SYMFONY COMPONENTS

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

    Guard
  49. Victoria Quirante @vicqr victoria@limenius.com HttpFoundation - The idea Defines an

    object-oriented layer for the HTTP specification
  50. Victoria Quirante @vicqr victoria@limenius.com HttpFoundation - The idea In PHP:

    - Request represented by global variables - Response generated by some functions Defines an object-oriented layer for the HTTP specification
  51. Victoria Quirante @vicqr victoria@limenius.com 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
  52. Victoria Quirante @vicqr victoria@limenius.com HttpFoundation - Request() Holds information about

    the client request use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals();
  53. Victoria Quirante @vicqr victoria@limenius.com HttpFoundation - Request() Creates Request object

    based on current PHP global variables use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals();
  54. Victoria Quirante @vicqr victoria@limenius.com 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 );
  55. Victoria Quirante @vicqr victoria@limenius.com 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') );
  56. Victoria Quirante @vicqr victoria@limenius.com Let’s create a POST endpoint When

    I request "POST /recipes" Then the response status code should be 201
  57. Victoria Quirante @vicqr victoria@limenius.com Let’s create a POST endpoint use

    Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $content = json_decode($request->getContent(), true);
  58. Victoria Quirante @vicqr victoria@limenius.com 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');
  59. Victoria Quirante @vicqr victoria@limenius.com 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
  60. And what about PSR-7?

  61. Victoria Quirante @vicqr victoria@limenius.com 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
  62. Victoria Quirante @vicqr victoria@limenius.com 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
  63. Victoria Quirante @vicqr victoria@limenius.com 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
  64. Victoria Quirante @vicqr victoria@limenius.com 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
  65. Victoria Quirante @vicqr victoria@limenius.com 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
  66. Serializer Getting representations of our objects, back and forth Serializer

    HttpFoundation Validator Form Guard
  67. Victoria Quirante @vicqr victoria@limenius.com Serializer - The idea $recipe =

    new Recipe(); $recipe->setName($content['name']); $recipe->setEnergy($content['energy']); $recipe->setServings($content['servings']); ... The request -> Our object (manual deserialization)
  68. Victoria Quirante @vicqr victoria@limenius.com 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)
  69. Victoria Quirante @vicqr victoria@limenius.com 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)
  70. Victoria Quirante @vicqr victoria@limenius.com 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
  71. Victoria Quirante @vicqr victoria@limenius.com 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
  72. Victoria Quirante @vicqr victoria@limenius.com Deserializing $recipe = new Recipe(); $recipe->setName($content['name']);

    $recipe->setEnergy($content['energy']); $recipe->setServings($content['servings']); Converting the JSON content request into an object
  73. Victoria Quirante @vicqr victoria@limenius.com 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
  74. Victoria Quirante @vicqr victoria@limenius.com Serializer - The idea Turns objects

    into a specific format, and the other way around
  75. Victoria Quirante @vicqr victoria@limenius.com 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);
  76. Victoria Quirante @vicqr victoria@limenius.com 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
  77. Representation in API != what we have in DB

  78. Victoria Quirante @vicqr victoria@limenius.com Representation in API != what we

    have in DB { id: 9, name: "victoriaq", password: "encryptedPassword", email: "victoria@limenius.com", avatar: "avatar.jpg", twitter_handler: "vicqr", profile: { id: 19, bio: "My bio." } }
  79. Victoria Quirante @vicqr victoria@limenius.com Representation in API != what we

    have in DB { id: 9, name: "victoriaq", password: "encryptedPassword", email: "victoria@limenius.com", avatar: "avatar.jpg", twitter_handler: "vicqr", profile: { id: 19, bio: "My bio." } } I may want to name this “username”
  80. Victoria Quirante @vicqr victoria@limenius.com Representation in API != what we

    have in DB { id: 9, name: "victoriaq", password: "encryptedPassword", email: "victoria@limenius.com", 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!
  81. Victoria Quirante @vicqr victoria@limenius.com Representation in API != what we

    have in DB { id: 9, name: "victoriaq", password: "encryptedPassword", email: "victoria@limenius.com", 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
  82. Victoria Quirante @vicqr victoria@limenius.com Representation in API != what we

    have in DB { id: 9, name: "victoriaq", password: "encryptedPassword", email: "victoria@limenius.com", 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
  83. Victoria Quirante @vicqr victoria@limenius.com Representation in API != what we

    have in DB { id: 9, name: "victoriaq", password: "encryptedPassword", email: "victoria@limenius.com", 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
  84. Victoria Quirante @vicqr victoria@limenius.com Representation in API != what we

    have in DB { id: 9, name: "victoriaq", password: "encryptedPassword", email: "victoria@limenius.com", 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
  85. Victoria Quirante @vicqr victoria@limenius.com Annotations MaxDepth - Detect and limit

    the serialization depth - Especially useful when serializing large trees
  86. Victoria Quirante @vicqr victoria@limenius.com 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
  87. Victoria Quirante @vicqr victoria@limenius.com 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 “”
  88. Victoria Quirante @vicqr victoria@limenius.com Let’s create a GET endpoint class

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

    Recipe { /** * @Groups({"detail", "overview"}) */ public $name; /** * @Groups({"detail"}) */ public $energy; /** * @Groups({"detail", "overview"}) */ public $servings; }
  90. Victoria Quirante @vicqr victoria@limenius.com 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
  91. Victoria Quirante @vicqr victoria@limenius.com Creating your custom normalizers For example,

    to serialize attributes with a different name
  92. Victoria Quirante @vicqr victoria@limenius.com 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
  93. Validator Enforcing sanity Validator HttpFoundation Serializer Form Guard

  94. Victoria Quirante @vicqr victoria@limenius.com 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
  95. Victoria Quirante @vicqr victoria@limenius.com 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)) ));
  96. Victoria Quirante @vicqr victoria@limenius.com 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)
  97. Victoria Quirante @vicqr victoria@limenius.com 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)
  98. Victoria Quirante @vicqr victoria@limenius.com 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?
  99. Victoria Quirante @vicqr victoria@limenius.com 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
  100. Victoria Quirante @vicqr victoria@limenius.com 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...
  101. Victoria Quirante @vicqr victoria@limenius.com Constraints https://symfony.com/doc/current/reference/constraints.html You have about 50

    constraints defined in the Validator component (from NotNull to ISBN…)
  102. Victoria Quirante @vicqr victoria@limenius.com 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
  103. Victoria Quirante @vicqr victoria@limenius.com 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
  104. Victoria Quirante @vicqr victoria@limenius.com 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; } https://knpuniversity.com/screencast/symfony-rest2
  105. Form Powering up validation and deserialization Form HttpFoundation Serializer Validator

    Guard
  106. Victoria Quirante @vicqr victoria@limenius.com 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
  107. Victoria Quirante @vicqr victoria@limenius.com Let’s create a PUT endpoint When

    I request "PUT /recipes/1" Then the response status code should be 200
  108. Victoria Quirante @vicqr victoria@limenius.com Let’s create a PUT endpoint $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; }
  109. Victoria Quirante @vicqr victoria@limenius.com Let’s create a PUT endpoint $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; }
  110. Victoria Quirante @vicqr victoria@limenius.com Let’s create a PUT endpoint $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; }
  111. Victoria Quirante @vicqr victoria@limenius.com Let’s create a PUT endpoint $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; }
  112. Victoria Quirante @vicqr victoria@limenius.com Let’s create a PUT endpoint $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
  113. POST to create and PUT to update?

  114. Victoria Quirante @vicqr victoria@limenius.com On PUT and POST Common knowledge:

    - POST to create resources - PUT to update resources
  115. Victoria Quirante @vicqr victoria@limenius.com On PUT and POST Common knowledge:

    - POST to create resources - PUT to update resources Not true
  116. Victoria Quirante @vicqr victoria@limenius.com On PUT and POST Common knowledge:

    - POST to create resources - PUT to update resources Not true (but somehow true)
  117. Victoria Quirante @vicqr victoria@limenius.com 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)
  118. Victoria Quirante @vicqr victoria@limenius.com 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
  119. And do I have to return the resource?

  120. Victoria Quirante @vicqr victoria@limenius.com Do I have to return the

    resource? Many say that we do not Some clients assume that we will
  121. Victoria Quirante @vicqr victoria@limenius.com 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
  122. Guard Authenticating Guard HttpFoundation Serializer Validator Form

  123. Victoria Quirante @vicqr victoria@limenius.com Guard - The idea Security Component

    allows to implement authentication Very powerful and flexible… But complex too Simplifies the authentication provided by the Security Component
  124. Victoria Quirante @vicqr victoria@limenius.com Just about one class with seven

    methods We only need to implement GuardAuthenticationInterface
  125. Victoria Quirante @vicqr victoria@limenius.com 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
  126. Victoria Quirante @vicqr victoria@limenius.com 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
  127. Victoria Quirante @vicqr victoria@limenius.com But at this point You need

    EventDispatcher, HttpKernel, Routing...
  128. Victoria Quirante @vicqr victoria@limenius.com But at this point You need

    EventDispatcher, HttpKernel, Routing... Starts looking like a good idea to use a complete framework
  129. III. USING THE FULL FRAMEWORK

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

  131. Victoria Quirante @vicqr victoria@limenius.com Components and bundles - Component ->

    decoupled and reusable library - Bundle -> tied to the Symfony Framework
  132. Victoria Quirante @vicqr victoria@limenius.com 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 +
  133. Victoria Quirante @vicqr victoria@limenius.com 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
  134. Victoria Quirante @vicqr victoria@limenius.com JMSSerializerBundle

  135. Victoria Quirante @vicqr victoria@limenius.com 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
  136. Victoria Quirante @vicqr victoria@limenius.com 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
  137. Victoria Quirante @vicqr victoria@limenius.com FOSRestBundle

  138. Victoria Quirante @vicqr victoria@limenius.com FOSRestBundle Set of tools: - View

    layer that deals with the different representations - Body request decoder - Listener that performs content negotiation - Parameter validator and converter - Exception decoder - Unified REST routing system
  139. Victoria Quirante @vicqr victoria@limenius.com FOSRestBundle Set of tools: - View

    layer that deals with the different representations - Body request decoder - Listener that performs content negotiation - Parameter validator and converter - Exception decoder - Unified REST routing system You can live without it, but it is quite useful when you have it
  140. Victoria Quirante @vicqr victoria@limenius.com LexikJWTAuthenticationBundle

  141. Victoria Quirante @vicqr victoria@limenius.com 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).
  142. Victoria Quirante @vicqr victoria@limenius.com 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
  143. Victoria Quirante @vicqr victoria@limenius.com 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
  144. Victoria Quirante @vicqr victoria@limenius.com 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"}
  145. Victoria Quirante @vicqr victoria@limenius.com 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
  146. Victoria Quirante @vicqr victoria@limenius.com BazingaHateoasBundle

  147. Victoria Quirante @vicqr victoria@limenius.com BazingaHateoasBundle Allows you to reach REST

    level 3 Hypermedia As The Engine Of Application State
  148. Victoria Quirante @vicqr victoria@limenius.com 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 }
  149. Victoria Quirante @vicqr victoria@limenius.com 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! :-)
  150. Victoria Quirante @vicqr victoria@limenius.com 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 { … }
  151. Victoria Quirante @vicqr victoria@limenius.com 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
  152. And do I actually HAVE TO reach level 3?

  153. Victoria Quirante @vicqr victoria@limenius.com 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:
  154. Victoria Quirante @vicqr victoria@limenius.com 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
  155. Victoria Quirante @vicqr victoria@limenius.com 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
  156. Victoria Quirante @vicqr victoria@limenius.com NelmioAPIDocBundle

  157. Victoria Quirante @vicqr victoria@limenius.com NelmioAPIDocBundle Documentation bundle You create documentation

    while coding It uses code introspection a lot It comes with Swagger support
  158. Victoria Quirante @vicqr victoria@limenius.com NelmioAPIDocBundle Documentation bundle You create documentation

    while coding It uses code introspection a lot It comes with Swagger support Easy to keep the documentation updated
  159. Victoria Quirante @vicqr victoria@limenius.com 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) {
  160. Victoria Quirante @vicqr victoria@limenius.com NelmioAPIDocBundle

  161. Victoria Quirante @vicqr victoria@limenius.com NelmioAPIDocBundle

  162. V. WHAT’S NEW

  163. API Platform Victoria Quirante @vicqr victoria@limenius.com Built on top of

    SF full framework You can build an API in 40 seconds Integrated with Swagger UI
  164. API Platform Victoria Quirante @vicqr victoria@limenius.com

  165. API Platform Victoria Quirante @vicqr victoria@limenius.com $ composer create-project api-platform/api-platform

    bookshop-api $ cd bookshop-api $ php bin/console doctrine:database:create $ php bin/console doctrine:schema:create $ php bin/console server:run
  166. Symfony releases Victoria Quirante @vicqr victoria@limenius.com

  167. Symfony 4 & Symfony Flex It will be released in

    November 2017 It moves towards zero configuration time It makes as easy to create projects with lots or few dependencies Victoria Quirante @vicqr victoria@limenius.com
  168. Symfony 4 & Symfony Flex It will be released in

    November 2017 It moves towards zero configuration time It makes as easy to create projects with lots or few dependencies Very easily you can have a SF project with SF Flex and API Platform Victoria Quirante @vicqr victoria@limenius.com https://medium.com/@fabpot/symfony-4-a-small-screencast-cf6511823f
  169. VI. FINAL THOUGHTS

  170. Victoria Quirante @vicqr victoria@limenius.com Final thoughts (i) Whatever the choices

    you make building your API: Be consistent Take profit of using a common language
  171. Victoria Quirante @vicqr victoria@limenius.com Final thoughts (ii) 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
  172. Victoria Quirante @vicqr victoria@limenius.com Final thoughts (iii) Symfony 4 &

    Symfony Flex are going to make extremely easy to create an API project
  173. None
  174. Thanks! Victoria Quirante @vicqr victoria@limenius.com