Slide 1

Slide 1 text

Building RESTful APIs with Symfony Components Victoria Quirante

Slide 2

Slide 2 text

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]

Slide 3

Slide 3 text

I. INTRODUCTION

Slide 4

Slide 4 text

Why would I want my API to be RESTful?

Slide 5

Slide 5 text

Victoria Quirante @vicqr [email protected] Possible reasons AGAINST Perhaps I can think of a better way to structure an API

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Victoria Quirante @vicqr [email protected] Main reasons to go for REST REST makes the most of HTTP

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Victoria Quirante @vicqr [email protected] What you already know about REST

Slide 11

Slide 11 text

Victoria Quirante @vicqr [email protected] Sane way to approach REST

Slide 12

Slide 12 text

Victoria Quirante @vicqr [email protected] Sane way to approach REST 1. Learn the stuff that is commonly accepted

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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!

Slide 15

Slide 15 text

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!

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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?

Slide 18

Slide 18 text

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?

Slide 19

Slide 19 text

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?

Slide 20

Slide 20 text

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?

Slide 21

Slide 21 text

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?

Slide 22

Slide 22 text

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…

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

Why Symfony & REST?

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Victoria Quirante @vicqr [email protected] Why Symfony & REST? REST ♥ HTTP Symfony ♥ HTTP

Slide 31

Slide 31 text

Why Symfony Components?

Slide 32

Slide 32 text

Victoria Quirante @vicqr [email protected] Symfony is two things 1. A full stack framework

Slide 33

Slide 33 text

Victoria Quirante @vicqr [email protected] Symfony is two things 1. A full stack framework 2. A set of independent components

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Doctrine http://symfony.com/projects

Slide 37

Slide 37 text

Propel http://symfony.com/projects

Slide 38

Slide 38 text

Silex http://symfony.com/projects

Slide 39

Slide 39 text

Drupal http://symfony.com/projects

Slide 40

Slide 40 text

Laravel http://symfony.com/projects

Slide 41

Slide 41 text

Victoria Quirante @vicqr [email protected] Components that we are going to see in detail HttpFoundation Serializer Validator Form Guard

Slide 42

Slide 42 text

Victoria Quirante @vicqr [email protected] Where/when can you apply this knowledge - Working with the full Symfony framework

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Victoria Quirante @vicqr [email protected] See repository https://github.com/VictoriaQ/rest-symfony-components

Slide 47

Slide 47 text

II. SYMFONY COMPONENTS

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Victoria Quirante @vicqr [email protected] HttpFoundation - The idea Defines an object-oriented layer for the HTTP specification

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

Victoria Quirante @vicqr [email protected] HttpFoundation - Request() Holds information about the client request use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals();

Slide 53

Slide 53 text

Victoria Quirante @vicqr [email protected] HttpFoundation - Request() Creates Request object based on current PHP global variables use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals();

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

And what about PSR-7?

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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)

Slide 68

Slide 68 text

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)

Slide 69

Slide 69 text

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)

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

Victoria Quirante @vicqr [email protected] Serializer - The idea Turns objects into a specific format, and the other way around

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

Representation in API != what we have in DB

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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”

Slide 80

Slide 80 text

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!

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

Victoria Quirante @vicqr [email protected] Annotations MaxDepth - Detect and limit the serialization depth - Especially useful when serializing large trees

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

Victoria Quirante @vicqr [email protected] Creating your custom normalizers For example, to serialize attributes with a different name

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

Validator Enforcing sanity Validator HttpFoundation Serializer Form Guard

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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)

Slide 97

Slide 97 text

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)

Slide 98

Slide 98 text

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?

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

Form Powering up validation and deserialization Form HttpFoundation Serializer Validator Guard

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

POST to create and PUT to update?

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

Victoria Quirante @vicqr [email protected] On PUT and POST Common knowledge: - POST to create resources - PUT to update resources Not true (but somehow true)

Slide 117

Slide 117 text

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)

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

And do I have to return the resource?

Slide 120

Slide 120 text

Victoria Quirante @vicqr [email protected] Do I have to return the resource? Many say that we do not Some clients assume that we will

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

Guard Authenticating Guard HttpFoundation Serializer Validator Form

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

Victoria Quirante @vicqr [email protected] Just about one class with seven methods We only need to implement GuardAuthenticationInterface

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

Victoria Quirante @vicqr [email protected] But at this point You need EventDispatcher, HttpKernel, Routing...

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

III. USING THE FULL FRAMEWORK

Slide 130

Slide 130 text

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

Slide 131

Slide 131 text

Victoria Quirante @vicqr [email protected] Components and bundles - Component -> decoupled and reusable library - Bundle -> tied to the Symfony Framework

Slide 132

Slide 132 text

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 +

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

Victoria Quirante @vicqr [email protected] JMSSerializerBundle

Slide 135

Slide 135 text

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

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

Victoria Quirante @vicqr [email protected] FOSRestBundle

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

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

Slide 140

Slide 140 text

Victoria Quirante @vicqr [email protected] LexikJWTAuthenticationBundle

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

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

Slide 143

Slide 143 text

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

Slide 144

Slide 144 text

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

Slide 145

Slide 145 text

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

Slide 146

Slide 146 text

Victoria Quirante @vicqr [email protected] BazingaHateoasBundle

Slide 147

Slide 147 text

Victoria Quirante @vicqr [email protected] BazingaHateoasBundle Allows you to reach REST level 3 Hypermedia As The Engine Of Application State

Slide 148

Slide 148 text

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 }

Slide 149

Slide 149 text

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! :-)

Slide 150

Slide 150 text

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

Slide 151

Slide 151 text

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

Slide 152

Slide 152 text

And do I actually HAVE TO reach level 3?

Slide 153

Slide 153 text

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:

Slide 154

Slide 154 text

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

Slide 155

Slide 155 text

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

Slide 156

Slide 156 text

Victoria Quirante @vicqr [email protected] NelmioAPIDocBundle

Slide 157

Slide 157 text

Victoria Quirante @vicqr [email protected] NelmioAPIDocBundle Documentation bundle You create documentation while coding It uses code introspection a lot It comes with Swagger support

Slide 158

Slide 158 text

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

Slide 159

Slide 159 text

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

Slide 160

Slide 160 text

Victoria Quirante @vicqr [email protected] NelmioAPIDocBundle

Slide 161

Slide 161 text

Victoria Quirante @vicqr [email protected] NelmioAPIDocBundle

Slide 162

Slide 162 text

V. WHAT’S NEW

Slide 163

Slide 163 text

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

Slide 164

Slide 164 text

API Platform Victoria Quirante @vicqr [email protected]

Slide 165

Slide 165 text

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

Slide 166

Slide 166 text

Symfony releases Victoria Quirante @vicqr [email protected]

Slide 167

Slide 167 text

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]

Slide 168

Slide 168 text

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

Slide 169

Slide 169 text

VI. FINAL THOUGHTS

Slide 170

Slide 170 text

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

Slide 171

Slide 171 text

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

Slide 172

Slide 172 text

Victoria Quirante @vicqr [email protected] Final thoughts (iii) Symfony 4 & Symfony Flex are going to make extremely easy to create an API project

Slide 173

Slide 173 text

No content

Slide 174

Slide 174 text

Thanks! Victoria Quirante @vicqr [email protected]