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

Victoria Quirante

May 30, 2017
Tweet

More Decks by Victoria Quirante

Other Decks in Programming

Transcript

  1. 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]
  2. Victoria Quirante @vicqr [email protected] Possible reasons AGAINST Perhaps I can

    think of a better way to structure an API REST seems very controversial and confusing in some points
  3. Victoria Quirante @vicqr [email protected] Main reasons to go for REST

    REST makes the most of HTTP It means to have a common language
  4. Victoria Quirante @vicqr [email protected] Main reasons to go for REST

    REST makes the most of HTTP It means to have a common language These are very powerful reasons
  5. Victoria Quirante @vicqr [email protected] Sane way to approach REST 1.

    Learn the stuff that is commonly accepted Use nouns for the resources!
  6. Victoria Quirante @vicqr [email protected] Sane way to approach REST 1.

    Learn the stuff that is commonly accepted Use nouns for the resources! Use HTTP verbs!
  7. Victoria Quirante @vicqr [email protected] 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!
  8. Victoria Quirante @vicqr [email protected] Sane way to approach REST 1.

    Learn the stuff that is commonly accepted 2. Be aware of the grey areas / controversial points
  9. Victoria Quirante @vicqr [email protected] 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?
  10. Victoria Quirante @vicqr [email protected] 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?
  11. Victoria Quirante @vicqr [email protected] 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?
  12. Victoria Quirante @vicqr [email protected] 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?
  13. Victoria Quirante @vicqr [email protected] 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?
  14. Victoria Quirante @vicqr [email protected] 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…
  15. Victoria Quirante @vicqr [email protected] 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
  16. Victoria Quirante @vicqr [email protected] 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
  17. Victoria Quirante @vicqr [email protected] 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
  18. Victoria Quirante @vicqr [email protected] 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
  19. Victoria Quirante @vicqr [email protected] Symfony is two things 1. A

    full stack framework 2. A set of independent components
  20. Victoria Quirante @vicqr [email protected] 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
  21. Victoria Quirante @vicqr [email protected] Components that we are going to

    see in detail HttpFoundation Serializer Validator Form Guard
  22. Victoria Quirante @vicqr [email protected] Where/when can you apply this knowledge

    - Working with the full Symfony framework - Using some of these components in some other framework
  23. Victoria Quirante @vicqr [email protected] 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
  24. Victoria Quirante @vicqr [email protected] 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
  25. Victoria Quirante @vicqr [email protected] HttpFoundation - The idea In PHP:

    - Request represented by global variables - Response generated by some functions Defines an object-oriented layer for the HTTP specification
  26. Victoria Quirante @vicqr [email protected] 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
  27. Victoria Quirante @vicqr [email protected] HttpFoundation - Request() Holds information about

    the client request use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals();
  28. Victoria Quirante @vicqr [email protected] HttpFoundation - Request() Creates Request object

    based on current PHP global variables use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals();
  29. Victoria Quirante @vicqr [email protected] 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 );
  30. Victoria Quirante @vicqr [email protected] 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') );
  31. Victoria Quirante @vicqr [email protected] Let’s create a POST endpoint When

    I request "POST /recipes" Then the response status code should be 201
  32. Victoria Quirante @vicqr [email protected] Let’s create a POST endpoint use

    Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $content = json_decode($request->getContent(), true);
  33. Victoria Quirante @vicqr [email protected] 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');
  34. Victoria Quirante @vicqr [email protected] 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
  35. Victoria Quirante @vicqr [email protected] 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
  36. Victoria Quirante @vicqr [email protected] 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
  37. Victoria Quirante @vicqr [email protected] 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
  38. Victoria Quirante @vicqr [email protected] 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
  39. Victoria Quirante @vicqr [email protected] 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
  40. Victoria Quirante @vicqr [email protected] Serializer - The idea $recipe =

    new Recipe(); $recipe->setName($content['name']); $recipe->setEnergy($content['energy']); $recipe->setServings($content['servings']); ... The request -> Our object (manual deserialization)
  41. Victoria Quirante @vicqr [email protected] 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)
  42. Victoria Quirante @vicqr [email protected] 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)
  43. Victoria Quirante @vicqr [email protected] 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
  44. Victoria Quirante @vicqr [email protected] 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
  45. Victoria Quirante @vicqr [email protected] Deserializing $recipe = new Recipe(); $recipe->setName($content['name']);

    $recipe->setEnergy($content['energy']); $recipe->setServings($content['servings']); Converting the JSON content request into an object
  46. Victoria Quirante @vicqr [email protected] 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
  47. Victoria Quirante @vicqr [email protected] 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);
  48. Victoria Quirante @vicqr [email protected] 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
  49. Victoria Quirante @vicqr [email protected] 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." } }
  50. Victoria Quirante @vicqr [email protected] 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”
  51. Victoria Quirante @vicqr [email protected] 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!
  52. Victoria Quirante @vicqr [email protected] 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
  53. Victoria Quirante @vicqr [email protected] 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
  54. Victoria Quirante @vicqr [email protected] 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
  55. Victoria Quirante @vicqr [email protected] 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
  56. Victoria Quirante @vicqr [email protected] Annotations MaxDepth - Detect and limit

    the serialization depth - Especially useful when serializing large trees
  57. Victoria Quirante @vicqr [email protected] 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
  58. Victoria Quirante @vicqr [email protected] 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 “”
  59. Victoria Quirante @vicqr [email protected] Let’s create a GET endpoint class

    Recipe { /** * @Groups({"detail", "overview"}) */ public $name; /** * @Groups({"detail"}) */ public $energy; /** * @Groups({"detail", "overview"}) */ public $servings; }
  60. Victoria Quirante @vicqr [email protected] Let’s create a GET endpoint class

    Recipe { /** * @Groups({"detail", "overview"}) */ public $name; /** * @Groups({"detail"}) */ public $energy; /** * @Groups({"detail", "overview"}) */ public $servings; }
  61. Victoria Quirante @vicqr [email protected] 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
  62. Victoria Quirante @vicqr [email protected] 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
  63. Victoria Quirante @vicqr [email protected] 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
  64. Victoria Quirante @vicqr [email protected] 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)) ));
  65. Victoria Quirante @vicqr [email protected] 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)
  66. Victoria Quirante @vicqr [email protected] 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)
  67. Victoria Quirante @vicqr [email protected] 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?
  68. Victoria Quirante @vicqr [email protected] 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
  69. Victoria Quirante @vicqr [email protected] 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...
  70. Victoria Quirante @vicqr [email protected] 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
  71. Victoria Quirante @vicqr [email protected] 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
  72. Victoria Quirante @vicqr [email protected] 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
  73. Victoria Quirante @vicqr [email protected] 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
  74. Victoria Quirante @vicqr [email protected] Let’s create a PUT endpoint When

    I request "PUT /recipes/1" Then the response status code should be 200
  75. Victoria Quirante @vicqr [email protected] 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; }
  76. Victoria Quirante @vicqr [email protected] 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; }
  77. Victoria Quirante @vicqr [email protected] 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; }
  78. Victoria Quirante @vicqr [email protected] 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; }
  79. Victoria Quirante @vicqr [email protected] 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
  80. Victoria Quirante @vicqr [email protected] On PUT and POST Common knowledge:

    - POST to create resources - PUT to update resources
  81. Victoria Quirante @vicqr [email protected] On PUT and POST Common knowledge:

    - POST to create resources - PUT to update resources Not true
  82. Victoria Quirante @vicqr [email protected] On PUT and POST Common knowledge:

    - POST to create resources - PUT to update resources Not true (but somehow true)
  83. Victoria Quirante @vicqr [email protected] 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)
  84. Victoria Quirante @vicqr [email protected] 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
  85. Victoria Quirante @vicqr [email protected] Do I have to return the

    resource? Many say that we do not Some clients assume that we will
  86. Victoria Quirante @vicqr [email protected] 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
  87. Victoria Quirante @vicqr [email protected] Guard - The idea Security Component

    allows to implement authentication Very powerful and flexible… But complex too Simplifies the authentication provided by the Security Component
  88. Victoria Quirante @vicqr [email protected] Just about one class with seven

    methods We only need to implement GuardAuthenticationInterface
  89. Victoria Quirante @vicqr [email protected] 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
  90. Victoria Quirante @vicqr [email protected] 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
  91. Victoria Quirante @vicqr [email protected] But at this point You need

    EventDispatcher, HttpKernel, Routing... Starts looking like a good idea to use a complete framework
  92. Victoria Quirante @vicqr [email protected] Components and bundles - Component ->

    decoupled and reusable library - Bundle -> tied to the Symfony Framework
  93. Victoria Quirante @vicqr [email protected] 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 +
  94. Victoria Quirante @vicqr [email protected] 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
  95. Victoria Quirante @vicqr [email protected] 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
  96. Victoria Quirante @vicqr [email protected] 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
  97. Victoria Quirante @vicqr [email protected] 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
  98. Victoria Quirante @vicqr [email protected] 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
  99. Victoria Quirante @vicqr [email protected] 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).
  100. Victoria Quirante @vicqr [email protected] 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
  101. Victoria Quirante @vicqr [email protected] 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
  102. Victoria Quirante @vicqr [email protected] 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"}
  103. Victoria Quirante @vicqr [email protected] 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
  104. Victoria Quirante @vicqr [email protected] 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 }
  105. Victoria Quirante @vicqr [email protected] 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! :-)
  106. Victoria Quirante @vicqr [email protected] 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 { … }
  107. Victoria Quirante @vicqr [email protected] 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
  108. Victoria Quirante @vicqr [email protected] 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:
  109. Victoria Quirante @vicqr [email protected] 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
  110. Victoria Quirante @vicqr [email protected] 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
  111. Victoria Quirante @vicqr [email protected] NelmioAPIDocBundle Documentation bundle You create documentation

    while coding It uses code introspection a lot It comes with Swagger support
  112. Victoria Quirante @vicqr [email protected] 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
  113. Victoria Quirante @vicqr [email protected] 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) {
  114. API Platform Victoria Quirante @vicqr [email protected] Built on top of

    SF full framework You can build an API in 40 seconds Integrated with Swagger UI
  115. API Platform Victoria Quirante @vicqr [email protected] $ 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
  116. 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 [email protected]
  117. 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 [email protected] https://medium.com/@fabpot/symfony-4-a-small-screencast-cf6511823f
  118. Victoria Quirante @vicqr [email protected] Final thoughts (i) Whatever the choices

    you make building your API: Be consistent Take profit of using a common language
  119. Victoria Quirante @vicqr [email protected] 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
  120. Victoria Quirante @vicqr [email protected] Final thoughts (iii) Symfony 4 &

    Symfony Flex are going to make extremely easy to create an API project