IoC container - CODEid Odessa 2018

IoC container - CODEid Odessa 2018

Did you know your IoC container can do a whole lot more than just constructor injection? Besides that it is actually packed with features. Inflectors, resolving callbacks, aliasing, method invocation to name a few. In this talk you will learn how to leverage the power of a great container and service providers to write better, loosely coupled code. Well designed code put together by your IoC container will make your applications SOLID, modular, lean and decoupled from the framework!

39eb3f3d313b13f05534e496285040b8?s=128

Hannes Van De Vreken

December 08, 2018
Tweet

Transcript

  1. IOC container @hannesvdvreken @laravelliveuk IOC container Hannes Van De Vreken

  2. Hi, my name is Hannes.

  3. !

  4. IoC Container

  5. IoC Container

  6. I O C

  7. International O C

  8. International Olympic C

  9. International Olympic Committee

  10. International Olympic Committee

  11. IoC Container

  12. IoC Container

  13. IoC Container

  14. WHAT IS IOC? IoC == Inversion of Control

  15. WHAT IS IOC? $service = new Service(); $service->calculate(); $service =

    $container->get(Service::class); $service->calculate();
  16. WHAT IS IOC? class Service { public function __construct() {

    init(); } public function init() { $config = config(‘ftpsettings…’); $this->ftp = new FTP(…); } public function calculate() {…} }
  17. But first: Refresh your knowledge of Design patterns WHAT IS

    IOC?
  18. class Service { public function init() { // setup FTP

    } public function calculate() { // Get stuff from FTP. // Do calculations. // Return result. } } WHAT IS IOC?
  19. class Service { public function __construct(Ftp $ftp) { } public

    function calculate() { // Get stuff from FTP. // Do calculations. // Return result. } } WHAT IS IOC?
  20. class Service { public function __construct(Ftp $ftp) { } public

    function calculate() { // Get stuff from FTP. // Add caching to prevent hitting FTP over and over again // Do calculations. // Return result. } } WHAT IS IOC?
  21. class Service { public function __construct(Ftp $ftp) { } public

    function calculate() { // Get stuff from FTP. // Add caching to prevent hitting FTP over and over again // Log that FTP is accessed. // Do calculations. // Return result. } } WHAT IS IOC?
  22. class Service { public function __construct(Ftp $ftp) { } public

    function calculate() { // Get stuff from FTP. // Add caching to prevent hitting FTP over and over again // Log that FTP is accessed. // Do calculations. // Return result. } } WHAT IS IOC?
  23. class FtpStorage { public function __construct(Ftp $ftp) { $this->ftp =

    $ftp; } public function get($filename) { // Get it. // Cache it. // Log it. // Return it. } } WHAT IS IOC?
  24. class Service { public function __construct(FtpStorage $storage) {
 $this->storage =

    $storage; } public function calculate() { $value = $this->storage->get($key); // Do calculations. // Return result. } } WHAT IS IOC?
  25. interface Storage { public function get($filename); } WHAT IS IOC?

  26. class FtpStorage implements Storage { public function __construct(Ftp $ftp) {

    $this->ftp = $ftp; } public function get($filename) { … } } WHAT IS IOC?
  27. class Service { public function __construct(FtpStorage $storage) {
 $this->storage =

    $storage; } public function calculate() { $value = $this->storage->get($key); // Do calculations. // Return result. } } WHAT IS IOC?
  28. class Service { public function __construct(Storage $storage) {
 $this->storage =

    $storage; } public function calculate() { $value = $this->storage->get($key); // Do calculations. // Return result. } } WHAT IS IOC?
  29. class FtpStorage { public function __construct(Ftp $ftp) { $this->ftp =

    $ftp; } public function get($filename) { // Get it. // Cache it. // Log it. // Return it. } } WHAT IS IOC?
  30. class CacheDecorator implements Storage { public function __construct(Cache $cache, Storage

    $wrapped) {…} public function get($key) { return $this->cache->remember($key, 60, function () { return $this->wrapped->get($key); }); } } WHAT IS IOC?
  31. class LogDecorator implements Storage { public function __construct(LoggerInterface $log, Storage

    $wrapped) {…} public function get($key) { $this->log->info('Retrieved value for '.$key); return $this->wrapped->get($key); } } WHAT IS IOC?
  32. class FtpStorage { public function __construct(Ftp $ftp) { $this->ftp =

    $ftp; } public function get($filename) { // Get it. // Cache it. // Log it. // Return it. } } WHAT IS IOC?
  33. class FtpStorage { public function __construct(Ftp $ftp) { $this->ftp =

    $ftp; } public function get($filename) { // Get it. // Return it. } } WHAT IS IOC?
  34. FtpStorage Service depends on depends on Ftp WHAT IS IOC?

  35. FtpStorage Service depends on Ftp WHAT IS IOC? implements <interface>

    Storage depends on
  36. Service WHAT IS IOC? implements <interface> Storage depends on FtpStorage

    CacheDecorator LogDecorator S3Storage
  37. WHAT IS IOC? FtpStorage implements <interface> Storage Service depends on

    depends on Ftp FtpStorage ervice depends on depends on Ftp
  38. $service = new Service(); WHAT IS IOC?

  39. $service = new Service( new CacheDecorator( $cache, new LogDecorator( $logger,

    new FtpStorage( new Ftp($settings) ) ) ) ); WHAT IS IOC?
  40. $container->get(Service::class); 1) Make FtpStorage 2) Wrap it with LogDecorator 3)

    Wrap LogDecorator instance with CacheDecorator 4) New up Service with CacheDecorator instance. WHAT IS IOC?
  41. Dependency inversion: Interface + implementation Dependency injection: Injecting `Storage` into

    `Service` Inversion of Control: The container builds your objects and calls them WHAT IS IOC?
  42. Inversion of Control IoC container to the rescue WHAT IS

    IOC?
  43. Let the IoC container help you with Composition WHAT IS

    IOC?
  44. symfony/dependency-injection illuminate/container aura/di zendframework/zend-di pimple/pimple league/container bitexpert/disco … container-interop/container-interop psr/container

    (PSR-11) CONSTRUCTOR INJECTION
  45. PSR-11 namespace Psr\Container; interface ContainerInterface { public function get($id); public

    function has($id); }
  46. None
  47. Automatic object resolution

  48. $listener = $container->get(SendInvoice::class); $listener->listen($event); CONSTRUCTOR INJECTION

  49. Reflection CONSTRUCTOR INJECTION

  50. class SendInvoice { public function __construct(Mailer $mailer) { $this->mailer =

    $mailer; } public function listen(InvoiceGeneratedEvent $event) { … } } CONSTRUCTOR INJECTION
  51. Constructor injection CONSTRUCTOR INJECTION

  52. (new \ReflectionClass($classname)) ->getConstructor() ->getParameters(); CONSTRUCTOR INJECTION

  53. Recursively CONSTRUCTOR INJECTION

  54. CONSTRUCTOR INJECTION SendInvoice ➡ Mailer ➡ SMTP

  55. Auto wiring: - Laravel - Symfony - Zend - league/container

    - … CONSTRUCTOR INJECTION
  56. But what if our code depends on interfaces… The container

    can’t construct interfaces? CONSTRUCTOR INJECTION
  57. Helping the container

  58. Situation 1: When <<interface>> is type hinted, resolve <<class>> instead

    REGISTRATION - BINDING
  59. Solution: Aliasing. REGISTRATION - BINDING

  60. // Laravel $container->alias(MailerInterface::class, Mailer::class); REGISTRATION - BINDING

  61. // Laravel $container->bind(MailerInterface::class, function () { return $container->get(Mailer::class); }); REGISTRATION

    - BINDING
  62. // The PHP League $container->add(MailerInterface::class, Mailer::class); REGISTRATION - BINDING

  63. Situation 2: When <<class>> has non-type hinted constructor arguments. REGISTRATION

    - BINDING
  64. class Mailer { /** * @param string $username * @param

    string $password */ public function construct($username, $password) {…} } REGISTRATION - BINDING
  65. Solution: Define explicitly. REGISTRATION - BINDING

  66. // Laravel $container->bind(Mailer::class, function () { return new Mailer($username, $password);

    }); REGISTRATION - BINDING
  67. // Symfony $container->register(Mailer::class) ->addArgument($username) ->addArgument($password); REGISTRATION - BINDING

  68. // The PHP League $container->add(Mailer::class) ->addArgument($username) ->addArgument($password); REGISTRATION - BINDING

  69. Situation 3: When <<class>> is type hinted, always return the

    exact same object. REGISTRATION - BINDING
  70. Example: Global state in a - Translator (user’s locale) -

    Entity Manager - Event dispatcher REGISTRATION - BINDING
  71. Solution: Sharing an instance. REGISTRATION - BINDING

  72. // Laravel $container->singleton(EntityManagerInterface::class, function () … }); $container->bind($key, $function, true);

    REGISTRATION - BINDING
  73. // Symfony $container->register(EntityManager::class)->setShared(false); REGISTRATION - BINDING

  74. // The PHP League $container->add(EntityManager::class)->setShared(); $container->share(EntityManager::class); REGISTRATION - BINDING

  75. Situation 4: When different classes need a different object of

    the same interface. REGISTRATION - BINDING
  76. class PhotosController { public function __construct(FilesystemInterface $photos) { } }

    class FilesController { public function __construct(FilesystemInterface $files) { } } REGISTRATION - BINDING
  77. Solution: Contextual binding. REGISTRATION - BINDING

  78. // Laravel $container ->when(PhotosController::class) ->needs(FilesystemInterface::class) ->give(‘fs.photos'); $container->bind('fs.photos', function () {});

    $container->bind('fs.files', function () {}); REGISTRATION - CONTEXTUAL BINDING
  79. // Laravel $container->bind('fs.photos', …); $container->bind(PhotosController::class, function $fs = $container->get('fs.photos'); return

    new PhotosController($fs); }); REGISTRATION - BINDING
  80. // The PHP League $container->add('fs.photos', …); $container->add(PhotosController::class, function ( $fs

    = $container->get('fs.photos'); return new PhotosController($fs); }); REGISTRATION - BINDING
  81. Situation 5: When resolving <<interface>>, do some setup. REGISTRATION -

    BINDING
  82. Example 1: When resolving the EntityManager, register some ModelListeners. REGISTRATION

    - BINDING
  83. Example 2: interface EnvironmentAware { public function setEnv($env); } trait

    EnvironmentAware { public function setEnv($env) { $this->env = $env; } } REGISTRATION - CONTAINER EVENTS
  84. Solution: Container events / inflection / hooks / … REGISTRATION

    - BINDING
  85. // Laravel $container->resolving(function (EnvironmentAware $object) { $object->setEnv(app()->environment()); }); REGISTRATION -

    CONTAINER EVENTS
  86. // The PHP League $container->inflector(EnvironmentAware::class) ->invokeMethod(‘setenv’, [app()->environment()]); REGISTRATION - CONTAINER

    EVENTS
  87. Also useful for: - Quickly injecting stuff REGISTRATION - CONTAINER

    EVENTS
  88. interface CommandBusAware trait CommandBusAware interface EventDispatcherAware trait EventDispatcherAware REGISTRATION -

    CONTAINER EVENTS
  89. Also useful for: - Quickly injecting stuff REGISTRATION - CONTAINER

    EVENTS
  90. Also useful for: - Quickly injecting stuff - Injecting soft

    dependencies REGISTRATION - CONTAINER EVENTS
  91. $container—>resolving(function (Mailer $mailer) { $mailer->setQueueResolver(function () { return $container->get(Queue::class); });

    ); REGISTRATION - CONTAINER EVENTS
  92. None
  93. Also useful for: - Quickly injecting stuff - Injecting soft

    dependencies REGISTRATION - CONTAINER EVENTS
  94. Also useful for: - Quickly injecting stuff - Injecting soft

    dependencies - Setting configurable values REGISTRATION - CONTAINER EVENTS
  95. $container->resolving(function (Validator $validator) { $fileTypes = $container->get('config')->get(…) $validator->setAllowedFileTypes($fileTypes); ); REGISTRATION

    - CONTAINER EVENTS
  96. Where to put all this? REGISTRATION

  97. Service Providers! REGISTRATION

  98. REGISTRATION public function register() { // Bindings } public function

    boot() { // Macros, extensions, container events, … }
  99. REGISTRATION public function boot(View $view) { $view->composer(…); }

  100. REGISTRATION // The PHP League $container->addServiceProvider(AppServiceProvider::class);

  101. Using a container

  102. Retrieving objects CALLING THE CONTAINER

  103. // Psr\Container\ContainerInterface $container->has('fs'); $container->get('fs'); CALLING THE CONTAINER

  104. Retrieving objects, with arguments CALLING THE CONTAINER

  105. // Laravel $container->bind(‘fs', function ($args) { // Use $args[0] to

    see what folder to use. }); $container->get('fs', [‘images’]); CALLING THE CONTAINER
  106. // Laravel $container->bind(‘fs', function ($args) { // Use $args[0] to

    see what folder to use. }); $container->get('fs', [‘images']); $container->bind('fs.images', function () { return $container->get('fs', ['images']); CALLING THE CONTAINER
  107. Calling functions, with resolved params CALLING THE CONTAINER

  108. // Laravel $container->call($closure); $container->call(function (Mailer $mailer) {}); $container->call([$listener, 'handle']); $container->call(‘Listener::handle’);

    CALLING THE CONTAINER
  109. // Laravel $container->call($closure); $container->call(function (Mailer $mailer) {}); $container->call([$listener, 'handle']); $container->call(‘Listener::handle’);

    $container->call(‘Listener@handle'); CALLING THE CONTAINER
  110. (new ReflectionFunction($callback)) ->getParameters(); CALLING THE CONTAINER

  111. // Laravel $func = $container->wrap('Listener@handle'); $func(); CALLING THE CONTAINER

  112. public function wrap($callback) { return function () { return $this->call($callback);

    }; } CALLING THE CONTAINER
  113. Tagging CALLING THE CONTAINER

  114. // Laravel $container->tag('fs.images', 'filesystem'); $container->tag('fs.pdfs', 'filesystem'); $filesystems = $container->tagged('filesystem'); foreach

    ($filesystems as $filesystem) { … } CALLING THE CONTAINER
  115. // Laravel $container->tag(‘tool-1.dispatcher’, 'dispatcher'); $container->tag(‘tool-2.dispatcher’, 'dispatcher'); $dispatchers = $container->tagged(‘dispatcher'); foreach

    ($dispatchers as $dispatcher) { $listener->delegate($dispatcher); } CALLING THE CONTAINER
  116. // The PHP League $container->add(‘fs.images’)->addTag(‘filesystems') $container->tag(‘fs.pdfs’)->addTag(‘filesystems'); $filesystems = $container->get(‘filesystems'); foreach

    ($filesystems as $filesystem) { … } CALLING THE CONTAINER
  117. When to inject the container?

  118. Preferably only in - Event Dispatchers - Router - Command

    Bus - Factories - … CALLING THE CONTAINER
  119. Bonus

  120. Solving circular dependencies BONUS - CIRCULAR DEPENDENCIES

  121. $mailer = new Mailer(QueueInterface $queue); $queue = new Queue(MailerInterface $mailer);

    BONUS - CIRCULAR DEPENDENCIES
  122. $mailer = new Mailer(); $queue = new Queue(MailerInterface $mailer); $mailer->setQueueResolver($callable);

    BONUS - CIRCULAR DEPENDENCIES
  123. // Laravel $queue = $container->get(QueueInterface::class); $container->resolving(function (Mailer $mailer) { $mailer->setQueueResolver(function

    () { return $container->get(QueueInterface::class); }); }); BONUS - CIRCULAR DEPENDENCIES
  124. Environment aware injection // Laravel $container->bind(‘fs.photos’, function () { if

    (getenv(‘APP_ENV’) === ‘integration’) { // Fill in memory adapter // with integration testing files
 } else { // Normal connection to S3. } }); BONUS
  125. recap: Reflection Aliasing Soft dependencies Using container

  126. Thank you! @hannesvdvreken @laravelliveuk Thank you @hannesvdvreken

  127. Time for questions. @hannesvdvreken @laravelliveuk Time for questions @hannesvdvreken