Pro Yearly is on sale from $80 to $50! »

DrupalCon 2015: The Symfony Framework: Your Free New Toolkit

DrupalCon 2015: The Symfony Framework: Your Free New Toolkit

Learning a new framework or CMS is hard. So, we usually stick to one: I use only Symfony, you use only Drupal. And that's too bad, because we all want to use the best tool for the job. But because Drupal 8 and Symfony have so much in common, that's about to change.

In this talk, we'll get started in the Symfony Framework by building a little app that takes us through routing, controllers, responses, events and the service container. For D8 users, you'll start to understand just how easy it'll be to use Symfony (or Silex) for certain projects. And if you haven't used it yet, you'll get a tour into the most fundamental pieces of Drupal 8 (since they're shared with Symfony!). Your toolkit is about to expand, and that's a reason to celebrate.

F5dfeeef276fcfd4751f4063487a5a3f?s=128

weaverryan

May 12, 2015
Tweet

Transcript

  1. The Symfony Framework YOUR FREE NEW TOOLKIT

  2. > Husband of the much more talented @leannapelham knplabs.com twitter.com/weaverryan

    Hallo! > Lead contributor to the Symfony documentation
 > KnpLabs US - Symfony consulting, training & kumbaya > Writer for KnpUniversity.com awesome amazing PHP Tutorials!!!
  3. Act 1 Dancing on your own? @weaverryan

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

    = array(
 'title' => 'ROOOOOOAR!',
 'page callback' => 'favorite_dinosaur',
 );
 
 return $items;
 }
 
 function favorite_dinosaur() {
 return 'Triceratops';
 }
  5. Symfony, Silex, etc routes & controllers requests & responses service

    container @weaverryan
  6. And now Symfony, Silex, D8 @weaverryan

  7. @weaverryan 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();

  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();
 An entire application that says hallo! @weaverryan
  9. Configure your web server

  10. Or use the built-in PHP web server \o/ php -S

    localhost:8000 @weaverryan
  11. * The built-in PHP web server can be used with

    Drupal too! @weaverryan
  12. 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 @weaverryan
  13. 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/* @weaverryan
  14. 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();
 If the URI matches the route, Silex executes this function (the controller) @weaverryan
  15. 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 value of {name} is passed as an argument to the controller @weaverryan
  16. 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();
 We construct the page and celebrate! @weaverryan (or non-alcoholic beverage of your choice)
  17. 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 @weaverryan
  18. Act 2 Hello Symfony @weaverryan

  19. @weaverryan downloads the installer

  20. @weaverryan my_dir_name

  21. Symfony Project Structure configuration, templates PHP Classes 3rd Party Code

    @weaverryan
  22. @weaverryan

  23. @weaverryan

  24. Hi, I’m the Symfony PacMan ghost! Look, things are working,

    you just don’t have any pages yet. Get to it! @weaverryan
  25. Install ! Build a page! " @weaverryan

  26. hello_world:
 path: /hello/{name}
 defaults:
 _controller: AppBundle\…sayHelloAction
 AppBundle\Controller\PoliteController::sayHelloAction @weaverryan

  27. namespace AppBundle\Controller;
 
 use Symfony\Component\HttpFoundation\Response;
 
 class PoliteController
 {
 public

    function sayHelloAction($name)
 {
 return new Response('Hello '.$name);
 }
 }
 @weaverryan
  28. @weaverryan

  29. 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! @weaverryan
  30. Debugging? @weaverryan

  31. None
  32. @weaverryan

  33. @weaverryan

  34. @weaverryan

  35. None
  36. None
  37. Can we do even less work? @weaverryan

  38. // ...
 
 class PoliteController
 {
 /**
 * @Route("/hello/{name}", name="hello_world")


    */
 public function sayHelloAction($name)
 {
 return new Response('Hello '.$name);
 }
 }
 @weaverryan
  39. Act 3 Services and the “container” @weaverryan

  40. Services == Useful Objects @weaverryan

  41. The container == the object that contains all the services

    @weaverryan
  42. In Silex, Symfony & Drupal 8 there is a “container”.

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

    many useful services (objects)
  44. That’s 224 built-in services @weaverryan

  45. @weaverryan

  46. How do I get access to the container inside a

    controller? @weaverryan
  47. /**
 * @Route("/hello/{name}", name="hello_world")
 */
 public function sayHelloAction($name)
 {
 $html

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

    {{ myName }}!
 {% endblock %}
 @weaverryan
  49. 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! @weaverryan
  50. What else does Symfony do? @weaverryan

  51. Doctrine ORM $em = $this->container ->get('doctrine.orm.entity_manager'); 
 $post = $em->getRepository('AppBundle:Post')


    ->findOneBySlug($slug);
 
 // ...
 
 $em->persist($post);
 $em->flush();
 @weaverryan
  52. or just use the DBAL or PDO $conn = $this->container

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

    'choice', [
 'choices' => ['f' => 'Female', 'm' => 'Male']
 ])
 ->getForm();
 
 $form->handleRequest($request);
 if ($form->isValid()) {
 $data = $form->getData();
 // do some stuff
 }
 
 $html = $this->container->get('templating')->render(
 'user/register.html.twig',
 ['form' => $form->createView()]
 );
 
 return new Response($html);
  54. 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) }} @weaverryan
  55. or just do it yourself $email = $request->request->get('email');
 $username =

    $request->request->get('username');
 $gender = $request->request->get('gender'); @weaverryan
  56. … and infinitely more with community bundles @weaverryan

  57. Act 4 Creating your own Services @weaverryan

  58. Now we want to select a random greeting each time

    @weaverryan
  59. Put this logic in our controller? How about a flat

    function somewhere? @weaverryan
  60. 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);
 }
 }
  61. 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);
 } @weaverryan
  62. Could we log which greeting was chosen? @weaverryan

  63. @weaverryan

  64. public function sayHelloAction($name)
 {
 $greeter = new RandomGreeter();
 $greeting =

    $greeter->randomlyGreet($name);
 
 $this->container->get('logger')
 ->info('Created greeting: '.$greeting);
 
 // ...
 }
 @weaverryan
  65. Could we log from inside RandomGreeter? @weaverryan

  66. 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
  67. omg DEPENDENCY INJECTION @weaverryan

  68. class RandomGreeter
 {
 private $logger;
 
 public function __construct($logger)
 {


    $this->logger = $logger;
 }
 
 // ...
 }
 @weaverryan
  69. class RandomGreeter
 {
 private $logger;
 
 public function __construct(LoggerInterface $logger)


    {
 $this->logger = $logger;
 }
 
 // ...
 }
 @weaverryan If you’re feeling fancy and/or awesome
  70. 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);
 }
 }

  71. public function sayHelloAction($name)
 {
 $greeter = new RandomGreeter( $this->container->get('logger') );


    $greeting = $greeter->randomlyGreet($name);
 
 // ...
 } @weaverryan
  72. Act 5 Teach Symfony how to instantiate your services @weaverryan

  73. services:
 my_random_greeter:
 class: AppBundle\Greet\RandomGreeter
 arguments:
 - "@logger"
 @weaverryan

  74. services:
 my_random_greeter:
 class: AppBundle\Greet\RandomGreeter
 arguments:
 - "@logger"


  75. public function sayHelloAction($name)
 {
 /* $greeter = new RandomGreeter(
 $this->container->get('logger')


    ); */
 
 $greeter = $this->container ->get('my_random_greeter');
 $greeting = $greeter->randomlyGreet($name);
 
 // ...
 }
 @weaverryan
  76. Act 6 Events (extra credit) @weaverryan

  77. Just like Drupal “hooks”, Silex has events @weaverryan

  78. “Hi! When event XXXXX happens, execute this function. kthxbai” YOU

    CAN TELL SILEX @weaverryan
  79. 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) Request: GET /hello/Drupal! EVENT kernel.request @weaverryan
  80. What if we didn’t return a Response from the controller?

  81. public function sayHelloAction($name)
 {
 $greeter = $this->container ->get('my_random_greeter');
 $greeting =

    $greeter->randomlyGreet($name);
 
 return [
 'template' => 'polite/sayHello.html.twig',
 'variables' => ['theGreeting' => $greeting]
 ];
 } @weaverryan
  82. @weaverryan I’m so angry right now!!!!!

  83. class RenderArrayViewSubscriber implements EventSubscriberInterface
 {
 private $templating;
 
 public function

    __construct(EngineInterface $templating)
 {
 $this->templating = $templating;
 }
 
 public function onView()
 {
 // call me if the controller does not
 // return a Response
 }
 
 public static function getSubscribedEvents()
 {
 return ['kernel.view' => 'onView'];
 }
 }

  84. services:
 # ...
 
 listener.render_array_view_listener:
 class: AppBundle\EventListener\RenderArrayViewSubscriber
 arguments:
 - "@templating"


    tags:
 - { name: kernel.event_subscriber }
 @weaverryan
  85. 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->templating->render($template, $variables);
 
 $response = new Response($html);
 $event->setResponse($response);
 }
  86. Act 7 Build something amazing @weaverryan

  87. It’d be nice to learn by looking at a real,

    fully- feature application @weaverryan “
  88. @weaverryan

  89. None
  90. @weaverryan Use + the Symfony plugin http://bit.ly/phpstorm-symfony

  91. http://symfony.com/doc

  92. @weaverryan KnpUniversity.com Screencasts

  93. @weaverryan 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();
 Use Silex!
  94. Use D8 @weaverryan

  95. Act 8 , & @weaverryan

  96. PRINCIPAL THEMES • Request/Response • Routing/Controller • PHP Namespaces/Autoloading •

    Services/Container 
 • Events/Listeners 
 • Profiler All are the same in Silex, Drupal & Symfony @weaverryan
  97. You can use Silex to learn Drupal! @weaverryan

  98. You can use Silex to learn Symfony! @weaverryan

  99. You can use Symfony to learn Drupal! @weaverryan

  100. https://www.flickr.com/photos/zzpza/32697842 Finally, We have more tools to solve problems

  101. Ryan Weaver @weaverryan THANK YOU!