Enable highend SPAs using REST-APIs with Symfony 4

Enable highend SPAs using REST-APIs with Symfony 4

This talk describes how to prototype an API using Symfony 4's event cycle and introduces API Platform as a way for a production ready API built on top of the same concepts.

6a1345d8e6dd15b2c78eff0c331963b1?s=128

Denis Brumann

May 26, 2018
Tweet

Transcript

  1. ENABLE HIGHEND SINGLE PAGE APPLICATIONS USING REST-APIS
 WITH SYMFONY 4

    & FLEX
  2. DENIS BRUMANN denis.brumann@sensiolabs.de @dbrumann

  3. None
  4. $kernel = new Kernel($env, $debug); $request = Request::createFromGlobals(); $response =

    $kernel->handle($request); $response->send();
  5. Request Response call controller

  6. /** * @Route("/", name="catalog") */ public function showAction(): Response {

    $products = $this->getDoctrine()
 ->getRepository(Product::class)
 ->findAll(); return $this->render( 'catalog/show.html.twig', [ 'products' => $products, ] ); }
  7. /** * @Route("/", name="catalog") */ public function showAction(): Response {

    $products = $this->getDoctrine()
 ->getRepository(Product::class)
 ->findAll(); return $this->render( 'catalog/show.html.twig', [ 'products' => $products, ] ); }
  8. /** * @Route("/products", name="products") * @Method("GET") */ public function listProducts()

    { return $this->getDoctrine()
 ->getRepository(Product::class)
 ->findAll(); }
  9. /** * @Route("/products", name="products") * @Method("GET") */ public function listProducts()

    { return $this->getDoctrine()
 ->getRepository(Product::class)
 ->findAll(); } !❗
  10. None
  11. None
  12. None
  13. Request Response call controller kernel.
 view

  14. interface EventSubscriberInterface { public static function getSubscribedEvents(); }

  15. public static function getSubscribedEvents() { return [ 'kernel.view' => 'onKernelView',

    ]; }
  16. public function onKernelView(
 GetResponseForControllerResultEvent $event
 ) { $products = $event->getControllerResult();

    $response = new JsonResponse($products); $event->setResponse($response); }
  17. class JsonResponseHandler implements EventSubscriberInterface { public static function getSubscribedEvents() {

    return [ KernelEvents::VIEW => 'onKernelView', ]; } public function onKernelView(
 GetResponseForControllerResultEvent $event
 ) { $data = $event->getControllerResult(); $response = new JsonResponse($data); if ($data === null) { $response->setStatusCode(
 JsonResponse::HTTP_NO_CONTENT
 ); } $event->setResponse($response); } }
  18. None
  19. /** * @Route("/products/{id}", name="product_show") * @Method("GET") */ public function getProduct(int

    $id) { $product = $this->getDoctrine() ->getRepository(Product::class) ->find($id); if (!$product) { throw new NotFoundHttpException('...'); } return $product; }
  20. None
  21. Request Response call controller kernel.
 exception Exception

  22. public static function getSubscribedEvents() { return [ 'kernel.exception' => 'myMethod',

    ]; }
  23. $request = $event->getRequest(); if (strpos($request->getPathInfo(), '/api/') !== 0) { return;

    } $exception = $event->getException(); $error = [ 'type' => $this->detectErrorType($exception), // Warning! Passing the message is insecure! 'message' => $exception->getMessage(), ]; $response = new JsonResponse(
 $error,
 $this->detectStatusCode($exception)
 ); $event->setResponse($response);
  24. None
  25. None
  26. None
  27. /** * @Route("/products", name="products_create") * @Method("POST") */ public function createProduct(Product

    $product) { $manager = $this->getDoctrine()->getManager(); $manager->persist($product); $manager->flush(); return new JsonResponse( null, JsonResponse::HTTP_CREATED ); }
  28. curl -i http://localhost:8000/api/products -X "POST" -H "Content-Type: application/json" -d '{"name":

    "Mystery Box", "price": 3999}'
  29. Request Response call controller kernel.
 view kernel.
 exception Exception resolve

    arguments
  30. interface ArgumentValueResolverInterface { /** * Whether this resolver can resolve

    the value for the given
 * ArgumentMetadata. * * @param Request $request * @param ArgumentMetadata $argument * * @return bool */ public function supports(Request $request, ArgumentMetadata $argument); /** * Returns the possible value(s). * * @param Request $request * @param ArgumentMetadata $argument * * @return \Generator */ public function resolve(Request $request, ArgumentMetadata $argument); }
  31. public function supports(
 Request $request,
 ArgumentMetadata $argument
 ) { return

    ( strpos($request->getPathInfo(), '/api/') === 0 && $argument->getType() === Product:class && !empty($request->getContent()) ); }
  32. public function resolve(Request $request, ArgumentMetadata $argument) { $data = $this->serializer->deserialize(

    $request->getContent(), $argument->getType(), 'json' ); $errors = $this->validator->validate($data, null, $groups); if (count($errors)) { throw new BadRequestHttpException((string) $errors); } yield $data; }
  33. None
  34. Request Response kernel.
 request kernel.
 response kernel.
 controller kernel.
 exception

    resolve controller call controller kernel.
 terminate Exception resolve arguments kernel.
 view SYMFONY KERNEL EVENTS
  35. None
  36. SYMFONY 4 BENEFITS No changes to services.yaml were needed! Only

    1 added dependency (orm-pack) Small build artefact due to fewer dependencies
  37. None
  38. PRODUCTION SETUP WITH
 API-PLATFORM

  39. None
  40. COMPOSER REQUIRE API

  41. None
  42. <?php namespace App\Entity; use ApiPlatform\Core\Annotation\ApiResource; use Doctrine\ORM\Mapping as ORM; use

    Symfony\Component\Validator\Constraints as Assert; /** * @ApiResource * @ORM\Entity */ class Rollercoaster { /** * @ORM\Id() * @ORM\GeneratedValue * @ORM\Column(type="integer") */ public $id; // ... }
  43. <?php namespace App\Entity; use ApiPlatform\Core\Annotation\ApiResource; use Doctrine\ORM\Mapping as ORM; use

    Symfony\Component\Validator\Constraints as Assert; /** * @ApiResource * @ORM\Entity */ class Rollercoaster { /** * @ORM\Id() * @ORM\GeneratedValue * @ORM\Column(type="integer") */ public $id; // ... }
  44. None
  45. None
  46. FEATURES Builds a fully featured API with JSON-LD, Hydra &

    GraphQL Auto-generated documentation with Swagger or Open API Authentication with JWT or OAuth HTTP Caching, CORS-support, schema.org-data model
  47. None
  48. Questions?

  49. Thanks!