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 full-size 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 full-size slide

  3. Act 1
    Dancing on your own?
    @weaverryan

    View full-size 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 full-size slide

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

    View full-size slide

  6. And now
    Symfony, Silex, D8
    @weaverryan

    View full-size 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 full-size 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 full-size slide

  9. Configure your
    web server

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

  18. Act 2
    Hello Symfony
    @weaverryan

    View full-size slide

  19. @weaverryan
    downloads the installer

    View full-size slide

  20. @weaverryan
    my_dir_name

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  23. Install !
    Build a page! "
    @weaverryan

    View full-size slide

  24. hello_world:

    path: /hello/{name}

    defaults:

    _controller: AppBundle\…sayHelloAction

    AppBundle\Controller\PoliteController::sayHelloAction
    @weaverryan

    View full-size slide

  25. namespace AppBundle\Controller;


    use Symfony\Component\HttpFoundation\Response;


    class PoliteController

    {

    public function sayHelloAction($name)

    {

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

    }

    }

    @weaverryan

    View full-size slide

  26. 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 full-size slide

  27. Debugging?
    @weaverryan

    View full-size slide

  28. Can we do even less
    work?
    @weaverryan

    View full-size slide

  29. // ...


    class PoliteController

    {

    /**

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

    */

    public function sayHelloAction($name)

    {

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

    }

    }

    @weaverryan

    View full-size slide

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

    View full-size slide

  31. Services == Useful Objects
    @weaverryan

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  37. /**

    * @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 full-size slide

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


    {% block body %}

    Hello {{ myName }}!

    {% endblock %}

    @weaverryan

    View full-size slide

  39. 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 full-size slide

  40. What else does
    Symfony do?
    @weaverryan

    View full-size slide

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

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

    ->findOneBySlug($slug);


    // ...


    $em->persist($post);

    $em->flush();

    @weaverryan

    View full-size slide

  42. 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 full-size slide

  43. 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 full-size slide

  44. Forms
    {{ form_start(form) }}

    {{ form_row(form.email) }}

    {{ form_row(form.username) }}

    {{ form_row(form.gender) }}


    Do it!

    {{ form_end(form) }}
    @weaverryan

    View full-size slide

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

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

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

    View full-size slide

  46. … and infinitely more with
    community bundles
    @weaverryan

    View full-size slide

  47. Act 4
    Creating your own
    Services
    @weaverryan

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  50. 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 full-size slide

  51. 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 full-size slide

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

    View full-size slide

  53. public function sayHelloAction($name)

    {

    $greeter = new RandomGreeter();

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


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

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


    // ...

    }

    @weaverryan

    View full-size slide

  54. Could we log from
    inside RandomGreeter?
    @weaverryan

    View full-size slide

  55. 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 full-size slide

  56. omg
    DEPENDENCY INJECTION
    @weaverryan

    View full-size slide

  57. class RandomGreeter

    {

    private $logger;


    public function __construct($logger)

    {

    $this->logger = $logger;

    }


    // ...

    }

    @weaverryan

    View full-size slide

  58. class RandomGreeter

    {

    private $logger;


    public function __construct(LoggerInterface $logger)

    {

    $this->logger = $logger;

    }


    // ...

    }

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

    View full-size slide

  59. 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 full-size slide

  60. public function sayHelloAction($name)

    {

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

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


    // ...

    }
    @weaverryan

    View full-size slide

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

    View full-size slide

  62. services:

    my_random_greeter:

    class: AppBundle\Greet\RandomGreeter

    arguments:

    - "@logger"

    @weaverryan

    View full-size slide

  63. services:

    my_random_greeter:

    class: AppBundle\Greet\RandomGreeter

    arguments:

    - "@logger"


    View full-size slide

  64. public function sayHelloAction($name)

    {

    /* $greeter = new RandomGreeter(

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

    ); */


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

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


    // ...

    }

    @weaverryan

    View full-size slide

  65. Act 6
    Events
    (extra credit)
    @weaverryan

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  68. 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 full-size slide

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

    View full-size slide

  70. 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 full-size slide

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

    View full-size slide

  72. 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 full-size slide

  73. services:

    # ...


    listener.render_array_view_listener:

    class: AppBundle\EventListener\RenderArrayViewSubscriber

    arguments:

    - "@templating"

    tags:

    - { name: kernel.event_subscriber }

    @weaverryan

    View full-size slide

  74. 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 full-size slide

  75. Act 7
    Build something amazing
    @weaverryan

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  78. http://symfony.com/doc

    View full-size slide

  79. @weaverryan
    KnpUniversity.com Screencasts

    View full-size slide

  80. @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 full-size slide

  81. Use D8
    @weaverryan

    View full-size slide

  82. Act 8
    , &
    @weaverryan

    View full-size slide

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

    • Events/Listeners

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  88. Ryan Weaver
    @weaverryan
    THANK YOU!

    View full-size slide