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

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.

weaverryan

May 12, 2015
Tweet

More Decks by weaverryan

Other Decks in Technology

Transcript

  1. The Symfony Framework
    YOUR FREE NEW TOOLKIT

    View Slide

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

    View Slide

  3. Act 1
    Dancing on your own?
    @weaverryan

    View Slide

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

    }

    View Slide

  5. Symfony, Silex, etc
    routes & controllers
    requests & responses
    service container
    @weaverryan

    View Slide

  6. And now
    Symfony, Silex, D8
    @weaverryan

    View Slide

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


    View Slide

  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

    View Slide

  9. Configure your
    web server

    View Slide

  10. Or use the built-in PHP
    web server \o/
    php -S localhost:8000
    @weaverryan

    View Slide

  11. * The built-in PHP web server can
    be used with Drupal too!
    @weaverryan

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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)

    View Slide

  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

    View Slide

  18. Act 2
    Hello Symfony
    @weaverryan

    View Slide

  19. @weaverryan
    downloads the installer

    View Slide

  20. @weaverryan
    my_dir_name

    View Slide

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

    View Slide

  22. @weaverryan

    View Slide

  23. @weaverryan

    View Slide

  24. Hi, I’m the Symfony PacMan ghost!
    Look, things are working, you just
    don’t have any pages yet. Get to it!
    @weaverryan

    View Slide

  25. Install !
    Build a page! "
    @weaverryan

    View Slide

  26. hello_world:

    path: /hello/{name}

    defaults:

    _controller: AppBundle\…sayHelloAction

    AppBundle\Controller\PoliteController::sayHelloAction
    @weaverryan

    View Slide

  27. namespace AppBundle\Controller;


    use Symfony\Component\HttpFoundation\Response;


    class PoliteController

    {

    public function sayHelloAction($name)

    {

    return new Response('Hello '.$name);

    }

    }

    @weaverryan

    View Slide

  28. @weaverryan

    View Slide

  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

    View Slide

  30. Debugging?
    @weaverryan

    View Slide

  31. View Slide

  32. @weaverryan

    View Slide

  33. @weaverryan

    View Slide

  34. @weaverryan

    View Slide

  35. View Slide

  36. View Slide

  37. Can we do even less
    work?
    @weaverryan

    View Slide

  38. // ...


    class PoliteController

    {

    /**

    * @Route("/hello/{name}", name="hello_world")

    */

    public function sayHelloAction($name)

    {

    return new Response('Hello '.$name);

    }

    }

    @weaverryan

    View Slide

  39. Act 3
    Services and the
    “container”
    @weaverryan

    View Slide

  40. Services == Useful Objects
    @weaverryan

    View Slide

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

    View Slide

  42. In Silex, Symfony & Drupal 8
    there is a “container”.
    If you have it, you can use any
    service (useful object)
    @weaverryan

    View Slide

  43. In Symfony and Drupal 8
    The container is pre-loaded
    with many useful services
    (objects)

    View Slide

  44. That’s 224 built-in services
    @weaverryan

    View Slide

  45. @weaverryan

    View Slide

  46. How do I get access to the
    container inside a controller?
    @weaverryan

    View Slide

  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

    View Slide

  48. {% extends 'base.html.twig' %}


    {% block body %}

    Hello {{ myName }}!

    {% endblock %}

    @weaverryan

    View Slide

  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

    View Slide

  50. What else does
    Symfony do?
    @weaverryan

    View Slide

  51. Doctrine ORM
    $em = $this->container
    ->get('doctrine.orm.entity_manager');

    $post = $em->getRepository('AppBundle:Post')

    ->findOneBySlug($slug);


    // ...


    $em->persist($post);

    $em->flush();

    @weaverryan

    View Slide

  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

    View Slide

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

    View Slide

  54. Forms
    {{ form_start(form) }}

    {{ form_row(form.email) }}

    {{ form_row(form.username) }}

    {{ form_row(form.gender) }}


    Do it!

    {{ form_end(form) }}
    @weaverryan

    View Slide

  55. or just do it yourself
    $email = $request->request->get('email');

    $username = $request->request->get('username');

    $gender = $request->request->get('gender');
    @weaverryan

    View Slide

  56. … and infinitely more with
    community bundles
    @weaverryan

    View Slide

  57. Act 4
    Creating your own
    Services
    @weaverryan

    View Slide

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

    View Slide

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

    View Slide

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

    }

    }

    View Slide

  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

    View Slide

  62. Could we log which
    greeting was chosen?
    @weaverryan

    View Slide

  63. @weaverryan

    View Slide

  64. public function sayHelloAction($name)

    {

    $greeter = new RandomGreeter();

    $greeting = $greeter->randomlyGreet($name);


    $this->container->get('logger')

    ->info('Created greeting: '.$greeting);


    // ...

    }

    @weaverryan

    View Slide

  65. Could we log from
    inside RandomGreeter?
    @weaverryan

    View Slide

  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

    View Slide

  67. omg
    DEPENDENCY INJECTION
    @weaverryan

    View Slide

  68. class RandomGreeter

    {

    private $logger;


    public function __construct($logger)

    {

    $this->logger = $logger;

    }


    // ...

    }

    @weaverryan

    View Slide

  69. class RandomGreeter

    {

    private $logger;


    public function __construct(LoggerInterface $logger)

    {

    $this->logger = $logger;

    }


    // ...

    }

    @weaverryan
    If you’re feeling fancy
    and/or awesome

    View Slide

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

    }

    }


    View Slide

  71. public function sayHelloAction($name)

    {

    $greeter = new RandomGreeter(
    $this->container->get('logger')
    );

    $greeting = $greeter->randomlyGreet($name);


    // ...

    }
    @weaverryan

    View Slide

  72. Act 5
    Teach Symfony how to
    instantiate your services
    @weaverryan

    View Slide

  73. services:

    my_random_greeter:

    class: AppBundle\Greet\RandomGreeter

    arguments:

    - "@logger"

    @weaverryan

    View Slide

  74. services:

    my_random_greeter:

    class: AppBundle\Greet\RandomGreeter

    arguments:

    - "@logger"


    View Slide

  75. public function sayHelloAction($name)

    {

    /* $greeter = new RandomGreeter(

    $this->container->get('logger')

    ); */


    $greeter = $this->container
    ->get('my_random_greeter');

    $greeting = $greeter->randomlyGreet($name);


    // ...

    }

    @weaverryan

    View Slide

  76. Act 6
    Events
    (extra credit)
    @weaverryan

    View Slide

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

    View Slide

  78. “Hi! When event XXXXX
    happens, execute this
    function. kthxbai”
    YOU CAN TELL SILEX
    @weaverryan

    View Slide

  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

    View Slide

  80. What if we didn’t
    return a Response
    from the controller?

    View Slide

  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

    View Slide

  82. @weaverryan
    I’m so angry right now!!!!!

    View Slide

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

    }

    }


    View Slide

  84. services:

    # ...


    listener.render_array_view_listener:

    class: AppBundle\EventListener\RenderArrayViewSubscriber

    arguments:

    - "@templating"

    tags:

    - { name: kernel.event_subscriber }

    @weaverryan

    View Slide

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

    }

    View Slide

  86. Act 7
    Build something amazing
    @weaverryan

    View Slide

  87. It’d be nice to learn by
    looking at a real, fully-
    feature application
    @weaverryan

    View Slide

  88. @weaverryan

    View Slide

  89. View Slide

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

    View Slide

  91. http://symfony.com/doc

    View Slide

  92. @weaverryan
    KnpUniversity.com Screencasts

    View Slide

  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!

    View Slide

  94. Use D8
    @weaverryan

    View Slide

  95. Act 8
    , &
    @weaverryan

    View Slide

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

    • Events/Listeners

    • Profiler
    All are the same in Silex, Drupal & Symfony
    @weaverryan

    View Slide

  97. You can use Silex
    to learn Drupal!
    @weaverryan

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  101. Ryan Weaver
    @weaverryan
    THANK YOU!

    View Slide