Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Avatar for Denis Brumann

Denis Brumann

May 26, 2018
Tweet

More Decks by Denis Brumann

Other Decks in Programming

Transcript

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

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

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

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

    { return $this->getDoctrine()
 ->getRepository(Product::class)
 ->findAll(); } !❗
  5. 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); } }
  6. /** * @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; }
  7. $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);
  8. /** * @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 ); }
  9. 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); }
  10. public function supports(
 Request $request,
 ArgumentMetadata $argument
 ) { return

    ( strpos($request->getPathInfo(), '/api/') === 0 && $argument->getType() === Product:class && !empty($request->getContent()) ); }
  11. 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; }
  12. Request Response kernel.
 request kernel.
 response kernel.
 controller kernel.
 exception

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

    1 added dependency (orm-pack) Small build artefact due to fewer dependencies
  14. <?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; // ... }
  15. <?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; // ... }
  16. 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