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

Symfony Tunisia 2015 - Cycle de vie du container d’Injection de Dépendance

Symfony Tunisia 2015 - Cycle de vie du container d’Injection de Dépendance

Nous revoyons le cycle de vie du contanier d'injection dépendance dans le détail.

Sarah KHALIL

April 17, 2015
Tweet

More Decks by Sarah KHALIL

Other Decks in Technology

Transcript

  1. Fonctionnement interne du container d'injection de dépendance de Symfony Sarah

    Khalil 17 avril 2015 Symfony Tunisie 2015 Cycle de vie du container d’Injection de Dépendance
  2. Je ne parlerai pas de configuration, ni de bonnes pratiques,

    ni de la manipulation de services avec Symfony. Un tout petit peu
  3. SOLID : Open/Closed principle Une classe doit être ouverte à

    l’extension, fermée à la modification.
  4. SOLID : Liskov substitution principle Remplacer une dépendance sans rien

    affecter. Lors de la spécialisation, conserver les même prototypes de méthodes.
  5. Ne pas instancier les dépendances. Abstraction (Interface/Abstract) Implémentation SOLID :

    Dependency Injection principle Changer facilement d’implémentation Dépendance = Objet
  6. Un peu de vocabulaire • Service = Instance de classe,

    un objet. • Container d’injection dépendance Objet PHP en charge de l’instanciation des services.
  7. Types d’injection : Constructeur (1/3) class  NewsletterManager   {  

           private  $mailer;          public  function  __construct(MailerInterface  $mailer)          {                  $this-­‐>mailer  =  $mailer;          }   }   <container>          <services>                  <service  id="my_mailer"  class="Mailer"></service>                  <service  id="newsletter_manager"  class="NewsletterManager">                          <argument  type="service"  id="my_mailer"/>                  </service>          </services>   </container> Dépendance Déclaration de services Dépendance obligatoire
  8. Types d’injection : Setter (2/3) class  NewsletterManager   {  

           private  $mailer;          public  function  setMailer(MailerInterface  $mailer)          {                  $this-­‐>mailer  =  $mailer;          }   }   <?xml  version="1.0"  encoding="UTF-­‐8"  ?>   <container>          <services>                  <service  id="my_mailer"  class="Mailer"></service>                  <service  id="newsletter_manager"  class="NewsletterManager">                          <call  method="setMailer">                                  <argument  type="service"  id="my_mailer"  />                          </call>                  </service>          </services>   </container> Déclaration de services Dépendance Dépendance optionnelle
  9. Types d’injection : Propriété (3/3) class  NewsletterManager   {  

           public  $mailer;   }   <?xml  version="1.0"  encoding="UTF-­‐8"  ?>   <container>          <services>                  <service  id="my_mailer"  class="Mailer"></service>                  <service  id="newsletter_manager"  class="NewsletterManager">                          <property  name="mailer"  type="service"  id="my_mailer"  />                  </service>          </services>   </container> Déclaration de services Dépendance Non conseillé
  10. Types d’injection Types + - Constructeur • Typage • Certain

    d’avoir la dépendance • Toujours passer la dépendance • Sauf si $dep=null Setter • Dépendance optionnelle • Typage • Vérifier que la dépendance est bien là. Propriété - • Aucun moyen de s’assurer que la dépendance est bien injectée. • Typage impossible.
  11. Ce que nous avons l’habitude faire <?xml  version="1.0"  encoding="UTF-­‐8"  ?>

      <container>          <services>                  <service  id="my_mailer"  class="AppBundle\Mail\Mailer"></service>          </services>   </container> use  Symfony\Bundle\FrameworkBundle\Controller\Controller;   class  DefaultController  extends  Controller   {          public  function  mailAction()          {                  $this-­‐>get('my_mailer')-­‐>sendEmail();          }   } étape 1: déclaration de service étape 2 : faire appel au service
  12. Suivez la carte, je vous emmène ! Front Controller Kernel

    ContainerBuilder Container Vous êtes ici
  13. 3 Grandes étapes 1. Construction du container builder 2. Compilation

    du container builder 3. Récupérer le container
  14. Front Controller web/app.php ou web/app_dev.php use  Symfony\Component\ClassLoader\ApcClassLoader;   use  Symfony\Component\HttpFoundation\Request;

      $loader  =  require_once  __DIR__.'/../app/bootstrap.php.cache';   require_once  __DIR__.'/../app/AppKernel.php';   $kernel  =  new  AppKernel('prod',  false);   $kernel-­‐>loadClassCache();   $request  =  Request::createFromGlobals();   $response  =  $kernel-­‐>handle($request);   $response-­‐>send();   $kernel-­‐>terminate($request,  $response); Build Container
  15. Front controller web/app.php ou web/app_dev.php use  Symfony\Component\ClassLoader\ApcClassLoader;   use  Symfony\Component\HttpFoundation\Request;

      $loader  =  require_once  __DIR__.'/../app/bootstrap.php.cache';   require_once  __DIR__.'/../app/AppKernel.php';   $kernel  =  new  AppKernel('prod',  false);   $kernel-­‐>loadClassCache();   $request  =  Request::createFromGlobals();   $response  =  $kernel-­‐>handle($request);   $response-­‐>send();   $kernel-­‐>terminate($request,  $response);
  16. Kernel Symfony\Component\HttpKernel\Kernel Build Container public  function  handle(Request  $request  […])  

    {          if  (false  ===  $this-­‐>booted)  {                   $this-­‐>boot();          }          return  […]   }
  17. Kernel Symfony\Component\HttpKernel\Kernel public  function  boot()   {      if

     (true  ===  $this-­‐>booted)  {              return;      }      […]       $this-­‐>initializeBundles();      $this-­‐>initializeContainer();      foreach  ($this-­‐>getBundles()  as  $bundle)  {              $bundle-­‐>setContainer($this-­‐>container);              $bundle-­‐>boot();      }      $this-­‐>booted  =  true;   } Appelle registerBundles() d’AppKernel pour récupérer tous les bundles à initialiser
  18. Kernel Symfony\Component\HttpKernel\Kernel Build Container public  function  boot()   {  

       if  (true  ===  $this-­‐>booted)  {              return;      }      […]      $this-­‐>initializeBundles();       $this-­‐>initializeContainer();      foreach  ($this-­‐>getBundles()  as  $bundle)  {              $bundle-­‐>setContainer($this-­‐>container);              $bundle-­‐>boot();      }      $this-­‐>booted  =  true;   }
  19. Kernel Symfony\Component\HttpKernel\Kernel Build Container protected  function  initializeContainer()   {  

            $class  =  $this-­‐>getContainerClass();          $cache  =  new  ConfigCache($this-­‐>getCacheDir().'/'.$class.'.php',  $this-­‐>debug);          $fresh  =  true;          if  (!$cache-­‐>isFresh())  {                  $container  =  $this-­‐>buildContainer();                  $container-­‐>compile();                  $this-­‐>dumpContainer(                          $cache,                          $container,                          $class,                          $this-­‐>getContainerBaseClass()                  );                  $fresh  =  false;          }          require_once  $cache;          $this-­‐>container  =  new  $class();          $this-­‐>container-­‐>set('kernel',  $this);                    […]   }
  20. Kernel getContainerClass() Build Container Construit le nom de la classe

    de container app(Prod|Dev)(Debug)ProjectContainer
  21. app(Prod|Dev)(Debug)ProjectContainer Build Container class  appDevDebugProjectContainer  extends  Container   {  

           //…          public  function  __construct()          {                  $this-­‐>methodMap  =  array(                          'user_manager'  =>  'getUserManagerService',                          //…            }          //…          protected  function  getUserManagerService()          {                  return  $this-­‐>services['user_manager']  =                            new  \AppBundle\Service\UserManager();          }   }
  22. Kernel Symfony\Component\HttpKernel\Kernel Build Container protected  function  initializeContainer()   {  

           $class  =  $this-­‐>getContainerClass();    $cache  =  new  ConfigCache(          $this-­‐>getCacheDir().’/'.$class.’.php',          $this-­‐>debug    );          $fresh  =  true;          if  (!$cache-­‐>isFresh())  {                  $container  =  $this-­‐>buildContainer();                  $container-­‐>compile();                  $this-­‐>dumpContainer(                          $cache,                          $container,                          $class,                          $this-­‐>getContainerBaseClass()                  );                  $fresh  =  false;          }          require_once  $cache;          $this-­‐>container  =  new  $class();          $this-­‐>container-­‐>set('kernel',  $this);                    […]   }
  23. Un petit détour Symfony\Component\Config\ConfigCache Build Container Son métier : gestion

    des fichiers mis en cache isFresh() write() getMetaFile() On y reviendra
  24. Kernel Symfony\Component\HttpKernel\Kernel Build Container protected  function  initializeContainer()   {  

           $class  =  $this-­‐>getContainerClass();          $cache  =  new  ConfigCache($this-­‐>getCacheDir().'/'.$class.'.php',  $this-­‐>debug);          $fresh  =  true;          if  ( !$cache-­‐>isFresh())  {                  $container  =  $this-­‐>buildContainer();                  $container-­‐>compile();                  $this-­‐>dumpContainer(                          $cache,                          $container,                          $class,                          $this-­‐>getContainerBaseClass()                  );                  $fresh  =  false;          }          require_once  $cache;          $this-­‐>container  =  new  $class();          $this-­‐>container-­‐>set('kernel',  $this);                    […]   }
  25. isFresh() Symfony\Component\Config\ConfigCache Build Container Le cache est frais si :

    • app(Prod|Dev)(Debug)ProjectContainer.php existe • l’application n’est pas en mode debug • si le fichier app(Prod|Dev) (Debug)ProjectContainer.php.meta existe • Ressources n’ont pas changées depuis la dernier boot du kernel et ou et app(Prod|Dev)(Debug)ProjectContainer.php.meta contient la liste des resources ajoutées au containerBuilder, serialisée
  26. Kernel Symfony\Component\HttpKernel\Kernel Build Container protected  function  initializeContainer()   {  

           $class  =  $this-­‐>getContainerClass();          $cache  =  new  ConfigCache($this-­‐>getCacheDir().'/'.$class.'.php',  $this-­‐>debug);          $fresh  =  true;          if  (!$cache-­‐>isFresh())  {                   $container  =  $this-­‐>buildContainer();                  $container-­‐>compile();                  $this-­‐>dumpContainer(                          $cache,                          $container,                          $class,                          $this-­‐>getContainerBaseClass()                  );                  $fresh  =  false;          }          require_once  $cache;          $this-­‐>container  =  new  $class();          $this-­‐>container-­‐>set('kernel',  $this);                    […]   }
  27. protected  function  buildContainer()   {          //

     check  if  the  cache  directory  is  writable          $container  =  $this-­‐>getContainerBuilder();          $container-­‐>addObjectResource($this);          $this-­‐>prepareContainer($container);          $containerLoader  =  $this-­‐>getContainerLoader($container);          $cont  =  $this-­‐>registerContainerConfiguration($containerLoader);          if  (null  !==  $cont)  {                  $container-­‐>merge($cont);          }          $container-­‐>addCompilerPass(new  AddClassesToCachePass($this));          $container-­‐>addResource(new  EnvParametersResource(‘SYMFONY__’);          return  $container;   } Kernel Symfony\Component\HttpKernel\Kernel Build Container 2 1 3 4 5 6
  28. protected  function  buildContainer()   {          //

     check  if  the  cache  directory  is  writable          $container  =  $this-­‐>getContainerBuilder();          $container-­‐>addObjectResource($this);          $this-­‐>prepareContainer($container);          $containerLoader  =  $this-­‐>getContainerLoader($container);          $cont  =  $this-­‐>registerContainerConfiguration($containerLoader);          if  (null  !==  $cont)  {                  $container-­‐>merge($cont);          }          $container-­‐>addCompilerPass(new  AddClassesToCachePass($this));          $container-­‐>addResource(new  EnvParametersResource(‘SYMFONY__’);          return  $container;   } Kernel Symfony\Component\HttpKernel\Kernel Build Container 2 1 3 4 5 6
  29. protected  function  buildContainer()   {          //

     check  if  the  cache  directory  is  writable          $container  =  $this-­‐>getContainerBuilder();          $container-­‐>addObjectResource($this);          $this-­‐>prepareContainer($container);          $containerLoader  =  $this-­‐>getContainerLoader($container);          $cont  =  $this-­‐>registerContainerConfiguration($containerLoader);          if  (null  !==  $cont)  {                  $container-­‐>merge($cont);          }          $container-­‐>addCompilerPass(new  AddClassesToCachePass($this));          $container-­‐>addResource(new  EnvParametersResource(‘SYMFONY__’);          return  $container;   } Kernel Symfony\Component\HttpKernel\Kernel Build Container 2 1 3 4 5 6
  30. Instancier le ContainerBuilder Symfony\Component\HttpKernel\Kernel Build Container Si ocramius/proxy-manager • Permet

    le lazy loading des services. • Un ProxyInstanciator se charge de n’instancier un service que si c’est vraiment nécessaire. lazy=true
  31. Lazy loading Service A Service B Sans lazy loading :

    ce service est instancé même s’il n’est pas utilisé. Avec le lazy loading : ce service n’est instancé que s’il y a utilisation concrète.
  32. protected  function  buildContainer()   {          //

     check  if  the  cache  directory  is  writable          $container  =  $this-­‐>getContainerBuilder();          $container-­‐>addObjectResource($this);          $this-­‐>prepareContainer($container);          $containerLoader  =  $this-­‐>getContainerLoader($container);          $cont  =  $this-­‐>registerContainerConfiguration($containerLoader);          if  (null  !==  $cont)  {                  $container-­‐>merge($cont);          }          $container-­‐>addCompilerPass(new  AddClassesToCachePass($this));          $container-­‐>addResource(new  EnvParametersResource(‘SYMFONY__’);          return  $container;   } Kernel Symfony\Component\HttpKernel\Kernel Build Container 2 1 3 4 5 6
  33. buildContainer() 3/6 Symfony\Component\HttpKernel\Kernel Build Container Kernel::prepareContainer() Pour chaque bundle •

    Chargement des classes d’extensions [1] ! • Appel de la méthode build() [2] ! • Passe de compilation chargée d’appeler la méthode load() de toutes les extensions 3
  34. namespace  AppBundle\DependencyInjection;   use  […]   class  AppExtension  extends  Extension

      {      public  function  load(array  $configs,  ContainerBuilder   $container)      {          $configuration  =  new  Configuration();          $config  =  $this-­‐>processConfiguration($configuration,   $configs);          $loader  =  new  Loader\XmlFileLoader($container,  new   FileLocator(__DIR__.'/../Resources/config'));          $loader-­‐>load('services.xml');      }   }   Classe d’extension Rappel
  35. prepareContainer() Symfony\Component\HttpKernel\Kernel Build Container Point d’extension ! [2] Bundle::build(ContainerBuilder) Enregistrer

    les passes de compilation, d’autres extensions… tout ce que l’on souhaite ajouter au containerBuilder.
  36. protected  function  buildContainer()   {          //

     check  if  the  cache  directory  is  writable          $container  =  $this-­‐>getContainerBuilder();          $container-­‐>addObjectResource($this);          $this-­‐>prepareContainer($container);          $containerLoader  =  $this-­‐>getContainerLoader($container);          $cont  =  $this-­‐>registerContainerConfiguration($containerLoader);          if  (null  !==  $cont)  {                  $container-­‐>merge($cont);          }          $container-­‐>addCompilerPass(new  AddClassesToCachePass($this));          $container-­‐>addResource(new  EnvParametersResource(‘SYMFONY__’);          return  $container;   } Kernel Symfony\Component\HttpKernel\Kernel Build Container 2 1 3 4 5 6
  37. protected  function  buildContainer()   {          //

     check  if  the  cache  directory  is  writable          $container  =  $this-­‐>getContainerBuilder();          $container-­‐>addObjectResource($this);          $this-­‐>prepareContainer($container);          $containerLoader  =  $this-­‐>getContainerLoader($container);          $cont  =  $this-­‐>registerContainerConfiguration($containerLoader);          if  (null  !==  $cont)  {                  $container-­‐>merge($cont);          }          $container-­‐>addCompilerPass(new  AddClassesToCachePass($this));          $container-­‐>addResource(new  EnvParametersResource(‘SYMFONY__’);          return  $container;   } Kernel Symfony\Component\HttpKernel\Kernel Build Container 2 1 3 4 5 6
  38. Symfony\Component\HttpKernel\Kernel Build Container • Ajout d’une passe de compilation responsable

    de l’écriture du classes.map. • Ne contient que les classes d’extension. • Usage uniquement interne. buildContainer() 3/6 5
  39. protected  function  buildContainer()   {          //

     check  if  the  cache  directory  is  writable          $container  =  $this-­‐>getContainerBuilder();          $container-­‐>addObjectResource($this);          $this-­‐>prepareContainer($container);          $containerLoader  =  $this-­‐>getContainerLoader($container);          $cont  =  $this-­‐>registerContainerConfiguration($containerLoader);          if  (null  !==  $cont)  {                  $container-­‐>merge($cont);          }          $container-­‐>addCompilerPass(new  AddClassesToCachePass($this));          $container-­‐>addResource(new  EnvParametersResource(‘SYMFONY__’);          return  $container;   } Kernel Symfony\Component\HttpKernel\Kernel Build Container 2 1 3 4 5 6
  40. ContainerBuilder Symfony\Component\DependencyInjection Compile protected  function  initializeContainer()   {    

         $class  =  $this-­‐>getContainerClass();          $cache  =  new  ConfigCache($this-­‐>getCacheDir().'/'.$class.'.php',  $this-­‐>debug);          $fresh  =  true;          if  (!$cache-­‐>isFresh())  {                  $container  =  $this-­‐>buildContainer();                   $container-­‐>compile();                  $this-­‐>dumpContainer(                          $cache,                          $container,                          $class,                          $this-­‐>getContainerBaseClass()                  );                  $fresh  =  false;          }          require_once  $cache;          $this-­‐>container  =  new  $class();          $this-­‐>container-­‐>set('kernel',  $this);                    […]   }
  41. ContainerBuilder Symfony\Component\DependencyInjection Compile • Récupération du compiler • Symfony\Component\DependencyInjection\Compiler •

    process($containerBuilder) de toutes les passes de compilations [4] • Résout les paramètres « %xxxxxx% » • Ajout des paramètres dans le FrozenParameterBag Lecture seule
  42. Passe de compilation Compiler pass Compile Modifier la définition de

    services dans le ContainerBuilder http://symfony.com/doc/current/cookbook/service_container/compiler_passes.html Point d’extension ! [4] CompilerPassInterface::process() http://afsy.fr/avent/2013/05-conteneur-de-services-creer-ses-propres-tags
  43. ContainerBuilder Symfony\Component\DependencyInjection récupérer le container protected  function  initializeContainer()   {

             $class  =  $this-­‐>getContainerClass();          $cache  =  new  ConfigCache($this-­‐>getCacheDir().'/'.$class.'.php',  $this-­‐>debug);          $fresh  =  true;          if  (!$cache-­‐>isFresh())  {                  $container  =  $this-­‐>buildContainer();                  $container-­‐>compile();                   $this-­‐>dumpContainer(                  $cache,                  $container,                  $class,                  $this-­‐>getContainerBaseClass()          );                  $fresh  =  false;          }          require_once  $cache;          $this-­‐>container  =  new  $class();          $this-­‐>container-­‐>set('kernel',  $this);                    […]   }
  44. • Symfony\Component\DependencyInjection\Dumper\PhpDumper • Si => le proxyDumper est utilisé. •

    Le code est créé pour remplir le fichier app(Prod|Dev)(Debug)ProjectContainer. • Le fichier est écrit. récupérer le container Kernel Symfony\Component\HttpKernel
  45. app(Prod|Dev)(Debug)ProjectContainer.php class  appDevDebugProjectContainer  extends  Container   {      

       //…          public  function  __construct()          {                  $this-­‐>methodMap  =  array(                          'user_manager'  =>  'getUserManagerService',                          //…            }          //…          protected  function  getUserManagerService()          {                  return  $this-­‐>services['user_manager']  =                            new  \AppBundle\Service\UserManager();          }   } récupérer le container
  46. ContainerBuilder Symfony\Component\DependencyInjection Compile protected  function  initializeContainer()   {    

         $class  =  $this-­‐>getContainerClass();          $cache  =  new  ConfigCache($this-­‐>getCacheDir().'/'.$class.'.php',  $this-­‐>debug);          $fresh  =  true;          if  (!$cache-­‐>isFresh())  {                  $container  =  $this-­‐>buildContainer();                  $container-­‐>compile();                  $this-­‐>dumpContainer(                          $cache,                          $container,                          $class,                          $this-­‐>getContainerBaseClass()                  );                  $fresh  =  false;          }           require_once  $cache;      $this-­‐>container  =  new  $class();          $this-­‐>container-­‐>set('kernel',  $this);                    […]   }
  47. ContainerBuilder Symfony\Component\DependencyInjection protected  function  initializeContainer()   {      

       $class  =  $this-­‐>getContainerClass();          $cache  =  new  ConfigCache($this-­‐>getCacheDir().'/'.$class.'.php',  $this-­‐>debug);          $fresh  =  true;          if  (!$cache-­‐>isFresh())  {                  $container  =  $this-­‐>buildContainer();                  $container-­‐>compile();                  $this-­‐>dumpContainer(                          $cache,                          $container,                          $class,                          $this-­‐>getContainerBaseClass()                  );                  $fresh  =  false;          }          require_once  $cache;          $this-­‐>container  =  new  $class();         $this-­‐>container-­‐>set('kernel',  $this);                    […]   } Service synthetic
  48. Kernel Symfony\Component\HttpKernel\Kernel Build Container public  function  boot()   {  

       if  (true  ===  $this-­‐>booted)  {              return;      }      […]      $this-­‐>initializeBundles();      $this-­‐>initializeContainer();      foreach  ($this-­‐>getBundles()  as  $bundle)  {              $bundle-­‐>setContainer($this-­‐>container);              $bundle-­‐>boot();      }      $this-­‐>booted  =  true;   }
  49. 3 Grandes étapes 1. Construction du container builder 2. Compilation

    du container builder 3. Récupérer le container
  50. Points d’extension disponibles Quand ? Réécriture Kernel:buildContainer() Étape 1 Bundle::getContainerExtension()

    Kernel:buildContainer() Étape 1 Bundle::build() Kernel:buildContainer() Étape 1 AppKernel::registerContainerConfiguration() ContainerBuilder::compile() Étape 2 CompilerPassInterface::process()