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

Double your toolbox: the shared goodies of D8 a...

Nicolas Grekas
September 26, 2016

Double your toolbox: the shared goodies of D8 and Symfony

You already know that Symfony and Drupal share a lot of things, but you might not know what that means... or how dangerous it's about to make you!

In this talk, we'll look at the fundamental principles behind Drupal 8, which happen to be the same things that drive Symfony behind the scenes. These include routes and controllers, events and the all-important service, service container and (queue dramatic sound-effect) dependency injection. If these concepts are still fairly new to you, this talk is for you: master them all at once and unlock a world where one skillset will empower you to use D8 or the Symfony framework should that need arise. It's a wonderful, new shared world: let's make something amazing, together.

Nicolas Grekas

September 26, 2016
Tweet

More Decks by Nicolas Grekas

Other Decks in Technology

Transcript

  1. Drupal 7 /** Implements hook_menu() */ function dinosaur_menu() { $items['hello']

    = array( 'title' => 'ROOOOOOAR!', 'page callback' => 'favorite_dinosaur', ); return $items; } function favorite_dinosaur() { return 'Triceratops'; } @nicolasgrekas
  2. @nicolasgrekas require_once __DIR__.'/vendor/autoload.php'; $app = new Silex\Application(); $app->get('/hello/{name}', function($name) use($app)

    { return 'Hello '.$app->escape($name); }); $app->run(); An entire application that says hallo!
  3. Or use the built-in PHP web server \o/ php -S

    localhost:8000 @nicolasgrekas
  4. Request -> Response Framework Response: Hello Drupal! Routing: Determine a

    function that can create this page (the controller) Request: GET /hello/Drupal! The Controller: Our code: constructs the page @nicolasgrekas
  5. require_once __DIR__.'/vendor/autoload.php'; $app = new Silex\Application(); $app->get('/hello/{name}', function($name) use($app) {

    return 'Hello '.$app->escape($name); }); $app->run(); The route is matched when the URI is /hello/* @nicolasgrekas
  6. require_once __DIR__.'/vendor/autoload.php'; $app = new Silex\Application(); $app->get('/hello/{name}', function($name) use($app) {

    return 'Hello '.$app->escape($name); }); $app->run(); @nicolasgrekas If the URI matches the route, Silex executes this function (the controller)
  7. require_once __DIR__.'/vendor/autoload.php'; $app = new Silex\Application(); $app->get('/hello/{name}', function($name) use($app) {

    return 'Hello '.$app->escape($name); }); $app->run(); @nicolasgrekas The value of {name} is passed as an argument to the controller
  8. require_once __DIR__.'/vendor/autoload.php'; $app = new Silex\Application(); $app->get('/hello/{name}', function($name) use($app) {

    return 'Hello '.$app->escape($name); }); $app->run(); @nicolasgrekas We construct the page and celebrate! (or non-alcoholic beverage of your choice)
  9. Request -> Response Framework Response: Hello Drupal! Routing: Determine a

    function that can create this page (the controller) Request: GET /hello/Drupal! The Controller: Our code: constructs the page @nicolasgrekas
  10. Hi, I’m the Symfony PacMan ghost! Look, things are working,

    this page just doesn’t exist yet. So get to it!
  11. Request -> Response Framework The Controller: Our code: constructs the

    page Response: Hello Drupal! Routing: Determine a function that can create this page (the controller) Request: GET /hello/Drupal! @nicolasgrekas
  12. // ... class PoliteController { /** * @Route("/hello/{name}") */ public

    function sayHelloAction($name) { return new Response('Hello '.$name); } } @nicolasgrekas
  13. The container == an associative array that holds the services

    $logger = $container['logger']; @nicolasgrekas
  14. In Silex, Symfony & Drupal 8 there is a container.

    If you have it, you can use any service (useful object) @nicolasgrekas
  15. In Symfony & Drupal 8 The container is pre-loaded with

    many useful services (objects) @nicolasgrekas
  16. /** * @Route("/hello/{name}") */ public function sayHelloAction($name) { $html =

    $this->container->get('twig')->render( 'polite/sayHello.html.twig', ['myName' => $name] ); return new Response($html); } @nicolasgrekas
  17. {% extends 'base.html.twig' %} {% block body %} Hello {{

    myName }}! {% endblock %} @nicolasgrekas
  18. Request -> Response Framework The Controller: Our code: constructs the

    page Response: Hello Drupal! Container (with services) Routing: Determine a function that can create this page (the controller) Request: GET /hello/Drupal! @nicolasgrekas
  19. or just use the DBAL or PDO $conn = $this->container

    ->get('database_connection'); $sql = 'SELECT id, name FROM post'; $posts = $conn->fetchAll($sql); @nicolasgrekas
  20. $form = $this->container->get('form.factory') ->createBuilder() ->add('email', 'email') ->add('username', 'text') ->add('gender', 'choice',

    [ 'choices' => ['f' => 'Female', 'm' => 'Male'] ]) ->getForm(); if ($form->handleRequest($request)->isSubmitted() && $form->isValid()) { $data = $form->getData(); // do some stuff } $html = $this->container->get('twig')->render( 'user/register.html.twig', ['form' => $form->createView()] ); return new Response($html);
  21. Forms {{ form_start(form) }} {{ form_row(form.email) }} {{ form_row(form.username) }}

    {{ form_row(form.gender) }} <button type="submit">Do it!</button> {{ form_end(form) }} @nicolasgrekas
  22. or just do it yourself $email = $request->get('email'); $username =

    $request->get('username'); $gender = $request->get('gender'); @nicolasgrekas
  23. I want to select a random greeting each time Hello

    %s! Hey you %s! Hola %s! Que tal %s! @nicolasgrekas
  24. Or centralize it so it can be re-used? Create a

    flat function? @nicolasgrekas
  25. namespace AppBundle\Greet; class RandomGreeter { private static $greetings = [

    'Hello %s', 'Hola %s!', 'git blame. Ah, I knew it was %s!' ]; public function randomlyGreet($name) { $key = array_rand(self::$greetings); $greeting = self::$greetings[$key]; return sprintf($greeting, $name); } } @nicolasgrekas
  26. public function sayHelloAction($name) { $greeter = new RandomGreeter(); $greeting =

    $greeter->randomlyGreet($name); $html = $this->container->get('templating')->render( 'polite/sayHello.html.twig', ['theGreeting' => $greeting] ); return new Response($html); } @nicolasgrekas
  27. public function sayHelloAction($name) { $greeter = new RandomGreeter(); $greeting =

    $greeter->randomlyGreet($name); $this->container->get('logger') ->info('Created greeting: '.$greeting); // ... } @nicolasgrekas
  28. class RandomGreeter { public function randomlyGreet($name) { $key = array_rand(self::$greetings);

    $greeting = self::$greetings[$key]; $this->container->get('logger') ->info('Created greeting: '.$greeting); return sprintf($greeting, $name); } } There’s no container property! That’s magic only the controller has @nicolasgrekas
  29. class RandomGreeter { private $logger; public function __construct(LoggerInterface $logger) {

    $this->logger = $logger; } // ... } If you’re feeling fancy and/or awesome @nicolasgrekas
  30. class RandomGreeter { private $logger; // ... public function randomlyGreet($name)

    { $key = array_rand(self::$greetings); $greeting = self::$greetings[$key]; $this->logger ->info('Created greeting: '.$greeting); return sprintf($greeting, $name); } } @nicolasgrekas
  31. public function sayHelloAction($name) { $greeter = new RandomGreeter( $this->container->get('logger') );

    $greeting = $greeter->randomlyGreet($name); // ... } @nicolasgrekas
  32. public function sayHelloAction($name) { /* $greeter = new RandomGreeter( $this->container->get('logger')

    ); */ $greeter = $this->container ->get('my_random_greeter'); $greeting = $greeter->randomlyGreet($name); // ... } @nicolasgrekas
  33. EVENTS kernel.view kernel.response Request -> Response Framework The Controller: Our

    code: constructs the page Container (with services) EVENT kernel.controller Response: Hello Drupal! Routing: Determine a function that can create this page (the controller) EVENT kernel.request Request: GET /hello/Drupal! @nicolasgrekas
  34. public function sayHelloAction($name) { $greeter = $this->container ->get('my_random_greeter'); $greeting =

    $greeter->randomlyGreet($name); return [ 'template' => 'polite/sayHello.html.twig', 'variables' => ['theGreeting' => $greeting] ]; } @nicolasgrekas
  35. class RenderArrayViewSubscriber implements EventSubscriberInterface { private $twig; public function __construct(\Twig_Environment

    $twig) { $this->twig = $twig; } public function onView() { // call me if the controller does not // return a Response } public static function getSubscribedEvents() { return ['kernel.view' => 'onView']; } } @nicolasgrekas
  36. public function onView(GetResponseForControllerResultEvent $event) { $controllerResult = $event->getControllerResult(); if (!is_array($controllerResult))

    { return; } if (!isset($controllerResult['template'])) { return; } $template = $controllerResult['template']; $variables = $controllerResult['variables']; $html = $this->twig->render($template, $variables); $response = new Response($html); $event->setResponse($response); } @nicolasgrekas
  37. It’d be nice to learn by looking at a real,

    fully- featured application @nicolasgrekas
  38. Principle Themes: @nicolasgrekas I can’t remember what that presentation was

    about, but the following terms are now stuck in my head: aka
  39. TERMS TO REPEAT IN YOUR HEAD • Route -> Controller

    -> Response • Services & the Container • Events & Listeners @nicolasgrekas
  40. Drupal 8 & Symfony share • Request and Response objects

    • Route, Controller, Response • Event Listener System • Service and Container System • List all Services (debug:container) • List all routes (debug:router) • Web Debug Toolbar (with devel module) @nicolasgrekas