$30 off During Our Annual Pro Sale. View Details »

Symfony Internals

Symfony Internals

PHPBenelux 2018

Andreas Hucks

January 27, 2018
Tweet

More Decks by Andreas Hucks

Other Decks in Programming

Transcript

  1. Symfony Internals

    View Slide

  2. Symfony Internals
    Andreas Hucks
    CTO @ SensioLabs Germany

    View Slide

  3. 2008

    View Slide

  4. Conference Hotel

    View Slide

  5. Auditorium

    View Slide

  6. Lunch

    View Slide

  7. Socials

    View Slide

  8. VIP Lounge

    View Slide

  9. Wellness

    View Slide

  10. Survey

    View Slide

  11. Motivation

    View Slide

  12. What to expect

    View Slide

  13. The Request
    $kernel = new Kernel($env, $debug);
    $request = Request::createFromGlobals();
    $response = $kernel->handle($request);
    $response->send();

    View Slide

  14. HttpKernel
    $kernel->handle($request);

    View Slide

  15. Request-Response Flow
    Request Response
    Front

    Controller
    Kernel

    View Slide

  16. Symfony is not an

    MVC framework

    View Slide

  17. View Slide

  18. Symfony is a

    Request-Response Framework

    View Slide

  19. Request-Response Flow
    Request Response
    Front

    Controller
    Kernel

    View Slide

  20. Request-Response Flow
    Kernel

    View Slide

  21. Request-Response Flow
    Kernel
    kernel.request
    kernel.controller
    kernel.response
    kernel.exception kernel.view
    kernel.terminate

    View Slide

  22. EventDispatcher
    $dispatcher = new EventDispatcher();
    $dispatcher->addListener('kernel.request', $callable);

    View Slide

  23. EventDispatcher
    $dispatcher->dispatch(
    'kernel.request',
    $responseEvent
    );

    View Slide

  24. EventDispatcher
    $dispatcher->dispatch(
    'kernel.request',
    $responseEvent
    );
    payload
    What happened.

    View Slide

  25. Request-Response Flow
    Kernel
    kernel.request
    kernel.controller
    kernel.response
    kernel.exception kernel.view
    kernel.terminate

    View Slide

  26. GetResponseEvent
    class GetResponseEvent extends KernelEvent
    {
    public function getResponse()
    {
    return $this->response;
    }
    public function setResponse(Response $response)
    {
    $this->response = $response;
    $this->stopPropagation();
    }
    kernel.request

    View Slide

  27. RouterListener
    /**
    * @Route(path="/books/{id}", name="book_show")
    */
    $params = [
    '_controller' => ‘App\Controller\BookController::showAction’,
    '_route' => 'book_show',
    '_route_params' => [
    'id' => '37'
    ],
    'id' => ’37'
    ];
    kernel.request

    View Slide

  28. RouterListener
    $parameters = $this
    ->matcher->match($request->getPathInfo());
    $request->attributes->add($parameters);
    unset(
    $parameters[‘_route'],
    $parameters[‘_controller']
    );
    $request->attributes->set(
    '_route_params',
    $parameters
    );
    kernel.request

    View Slide

  29. LocaleListener
    $request = $event->getRequest();
    $request->setDefaultLocale($this->defaultLocale);
    if ($locale = $request->attributes->get('_locale')) {
    $request->setLocale($locale);
    }
    kernel.request

    View Slide

  30. Customize!
    • Content Negotiation - Parse headers and add attribute
    • Verify signed Cookies
    • Redirects based on arbitrary criteria
    • Custom Locale stuff
    • … ?

    View Slide

  31. Request-Response Flow
    Kernel
    kernel.request
    kernel.controller
    kernel.response
    kernel.exception kernel.view
    kernel.terminate

    View Slide

  32. FilterControllerEvent
    class FilterControllerEvent extends KernelEvent
    {
    public function getController()
    {
    return $this->controller;
    }
    public function setController(callable $controller)
    {
    $this->controller = $controller;
    }
    kernel.controller

    View Slide

  33. ControllerListener
    class ControllerListener implements EventSubscriberInterface
    {
    public function __construct(Reader $reader)
    {
    $this->reader = $reader;
    }
    public function onKernelController(FilterControllerEvent $event)
    {
    $controller = $event->getController();
    // read annotations for class and method
    $request = $event->getRequest();
    foreach ($configurations as $key => $attributes) {
    $request->attributes->set($key, $attributes);
    }
    kernel.controller

    View Slide

  34. ParamConverterListener
    class ParamConverterListener implements EventSubscriberInterface
    {
    /**
    * @var ParamConverterManager
    */
    private $manager;
    kernel.controller

    View Slide

  35. Request-Response Flow
    Kernel
    kernel.request
    kernel.controller
    kernel.response
    kernel.exception kernel.view
    kernel.terminate

    View Slide

  36. Wait… Controllers?

    View Slide

  37. HttpKernel
    $response = \call_user_func_array(
    $controller,
    $arguments
    );

    View Slide

  38. Request-Response Flow
    Kernel
    kernel.request
    kernel.controller
    kernel.response
    kernel.exception kernel.view
    kernel.terminate

    View Slide

  39. GetResponseForControllerResultEvent
    class GetResponseForControllerResultEvent extends GetResponseEvent
    {
    public function getControllerResult()
    {
    return $this->controllerResult;
    }
    public function setControllerResult($controllerResult)
    {
    $this->controllerResult = $controllerResult;
    }
    kernel.view

    View Slide

  40. TemplateListener
    public function onKernelView(
    GetResponseForControllerResultEvent $event
    ) {
    /* @var Template $template */
    $request = $event->getRequest();
    $template = $request->attributes->get(‘_template');
    // …
    $event->setResponse(
    new Response(
    $this->twig->render($template->getTemplate()),
    $parameters
    )
    );
    kernel.view

    View Slide

  41. Customize!
    • Auto serializing and encoding for web service responses
    • Together with kernel.controller: 

    Action Domain Responder, other patterns?

    View Slide

  42. Request-Response Flow
    Kernel
    kernel.request
    kernel.controller
    kernel.response
    kernel.exception kernel.view
    kernel.terminate

    View Slide

  43. GetResponseForExceptionEvent
    class GetResponseForExceptionEvent extends GetResponseEvent
    {
    public function getException()
    {
    return $this->exception;
    }
    kernel.exception

    View Slide

  44. ExceptionListener
    public function onKernelException(
    GetResponseForExceptionEvent $event
    ) {
    $exception = $event->getException();
    $request = $event->getRequest();
    $this->logException($exception
    kernel.exception

    View Slide

  45. Customize!
    • Customize for every exception type you like
    • Redirect for some errors
    • “were you looking for…” suggestions on 404 pages
    • Custom alerts
    • … ?

    View Slide

  46. Request-Response Flow
    Kernel
    kernel.request
    kernel.controller
    kernel.response
    kernel.exception kernel.view
    kernel.terminate

    View Slide

  47. FilterResponseEvent
    class FilterResponseEvent extends KernelEvent
    {
    public function getResponse()
    {
    return $this->response;
    }
    public function setResponse(Response $response)
    {
    $this->response = $response;
    }
    kernel.response

    View Slide

  48. WebDebugToolbarListener
    $response->headers->set(
    'X-Debug-Token-Link',
    $this->urlGenerator->generate(
    ‘_profiler',
    array(
    'token' => $response->headers->get(‘X-Debug-Token')
    ),
    UrlGeneratorInterface::ABSOLUTE_URL
    )
    );
    $this->injectToolbar($response, $request, $nonces);
    kernel.response

    View Slide

  49. SaveSessionListener
    $session = $event->getRequest()->getSession();
    if ($session && $session->isStarted()) {
    $session->save();
    $event->getResponse()
    ->setPrivate()
    ->setMaxAge(0)
    ->headers->addCacheControlDirective('must-revalidate');
    }
    kernel.response

    View Slide

  50. Customize!
    • Sign cookies
    • Add custom headers
    • … ?

    View Slide

  51. Request-Response Flow
    Kernel
    kernel.request
    kernel.controller
    kernel.response
    kernel.exception kernel.view
    kernel.terminate

    View Slide

  52. PostResponseEvent
    class PostResponseEvent extends KernelEvent
    {
    public function getResponse()
    {
    return $this->response;
    }
    kernel.terminate

    View Slide

  53. EmailSenderListener
    public function onTerminate()
    {
    // send spooled Emails
    kernel.terminate

    View Slide

  54. Other Listeners kernel.request
    • FirewallListener
    • Collector Listeners

    View Slide

  55. Recap
    Request Response
    Front

    Controller
    Kernel

    View Slide

  56. Container & Cache

    View Slide

  57. Container & Cache
    protected function getRouterService()
    {
    $this->services[‘router']
    = $instance
    = new \Symfony\Bundle\…\Router(
    $this, ‘/app/config/routing_dev.yml'
    )
    // [snip]
    return $instance;
    }

    View Slide

  58. Container & Cache
    Cache class for this
    request?
    Does it exists?
    require() it
    yes
    Build Container
    no

    View Slide

  59. Container & Cache
    Cache class for this
    request?
    Does it exists?
    require() it
    yes
    Build Container
    no

    View Slide

  60. Container Build
    Register Bundles
    Register Extensions &
    Compiler Passes
    Register Project Config
    Run Extensions
    Run compiler Passes Dump Container
    Run Cache Warmers
    (quite a few)

    View Slide

  61. Container Build
    Register Bundles
    Register Extensions &
    Compiler Passes
    Register Project Config
    Run Extensions
    Run compiler Passes Dump Container
    Run Cache Warmers

    View Slide

  62. Registering Bundles
    public function registerBundles()
    {
    $bundles = [
    new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
    new Symfony\Bundle\SecurityBundle\SecurityBundle(),
    new Symfony\Bundle\TwigBundle\TwigBundle(),
    // [snip]
    ];
    return $bundles;
    }

    View Slide

  63. Container Build
    Register Bundles
    Register Extensions &
    Compiler Passes
    Register Project Config
    Run Extensions
    Run compiler Passes Dump Container
    Run Cache Warmers

    View Slide

  64. Registering Extensions
    protected function prepareContainer(ContainerBuilder $container)
    {
    $extensions = array();
    foreach ($this->bundles as $bundle) {
    if ($extension = $bundle->getContainerExtension()) {
    $container->registerExtension($extension);
    $extensions[] = $extension->getAlias();
    }
    // [snip]
    }

    View Slide

  65. Container Build
    Register Bundles
    Register Extensions &
    Compiler Passes
    Register Project Config
    Run Extensions
    Run compiler Passes Dump Container
    Run Cache Warmers

    View Slide

  66. Register Project-Level
    Container Config
    public function registerContainerConfiguration(
    LoaderInterface $loader
    ) {
    $loader->load(
    $this->getRootDir().
    ’/config/config_’.$this->getEnvironment().'.yml'
    );
    }

    View Slide

  67. Container Build
    Register Bundles
    Register Extensions &
    Compiler Passes
    Register Project Config
    Run Extensions
    Run compiler Passes Dump Container
    Run Cache Warmers

    View Slide

  68. Extensions
    class TwigExtension extends Extension
    {
    public function load(array $configs, ContainerBuilder $container)
    {
    $loader = new XmlFileLoader(
    $container,
    new FileLocator(__DIR__.’/../Resources/config')
    );
    $loader->load('twig.xml');

    View Slide

  69. Customize!
    • Load config files
    • Do not load config files depending on parameter
    • Set Parameters, manipulate parameters
    • BETTER NOT manipulate services (they are not complete
    at this point, see next slides)

    View Slide

  70. Container Build
    Register Bundles
    Register Extensions &
    Compiler Passes
    Register Project Config
    Run Extensions
    Run compiler Passes Dump Container
    Run Cache Warmers

    View Slide

  71. Compiler Passes
    public function process(ContainerBuilder $container)
    {
    if (!$container->has('app.myservice')) {
    return;
    }
    $definition = $container->findDefinition(
    'app.myservice'
    );
    // find formatter service
    $definition->addMethodCall(
    'addFormatter',
    array(new Reference($formatterId))
    );
    }

    View Slide

  72. (A few) Compiler Passes
    • ServiceLocatorTagPass
    • DecoratorServicePass
    • ResolveParameterPlaceHoldersPass
    • ResolveFactoryClassPass
    • ResolveNamedArgumentsPass
    • AutowirePass
    • RemovePrivateAliasesPass
    • ReplaceAliasByActualDefinitionPass
    • RemoveAbstractDefinitionsPass
    • InlineServiceDefinitionsPass
    • RemoveUnusedDefinitionsPass

    View Slide

  73. (A few) Compiler Passes
    • ServiceLocatorTagPass
    • DecoratorServicePass
    • ResolveParameterPlaceHoldersPass
    • ResolveFactoryClassPass
    • ResolveNamedArgumentsPass
    • AutowirePass
    • RemovePrivateAliasesPass
    • ReplaceAliasByActualDefinitionPass
    • RemoveAbstractDefinitionsPass
    • InlineServiceDefinitionsPass
    • RemoveUnusedDefinitionsPass

    View Slide

  74. Customize!
    • Add method calls to services based on tags
    • Manipulate services that depend on the full service config

    View Slide

  75. Container Build
    Register Bundles
    Register Extensions &
    Compiler Passes
    Register Project Config
    Run Extensions
    Run compiler Passes Dump Container
    Run Cache Warmers

    View Slide

  76. Dump Container
    protected function dumpContainer(
    ConfigCache $cache,
    ContainerBuilder $container,
    $class,
    $baseClass
    ) {
    $dumper = new PhpDumper($container);
    $content = $dumper->dump(/*…*/);

    View Slide

  77. … and use it
    $this->container = require $cache->getPath();

    View Slide

  78. Container Build
    Register Bundles
    Register Extensions &
    Compiler Passes
    Register Project Config
    Run Extensions
    Run compiler Passes Dump Container
    Run Cache Warmers

    View Slide

  79. Cache Warmers
    public function warmUp($cacheDir)
    {
    $filesystem = new Filesystem();
    $templates = array();
    foreach ($this->finder->findAllTemplates() as $template) {
    // [snip]
    }
    // [snip]
    $this->writeCacheFile($cacheDir.’/templates.php', // [snip]
    }

    View Slide

  80. Customize!
    • You have full access to services, the container in ready
    • Create custom caches your application needs
    • Prefetch and cache data from external sources

    View Slide

  81. View Slide

  82. Thank you :)
    https://joind.in/talk/bd64e

    View Slide