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

IoC container - LaravelLive UK 2018

IoC container - LaravelLive UK 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

June 12, 2018
Tweet

Transcript

  1. IOC container @hannesvdvreken @laravelliveuk

  2. Hi, my name is Hannes.

  3. !

  4. IoC Container

  5. I O C

  6. International O C

  7. International Olympic C

  8. International Olympic Committee

  9. International Olympic Committee

  10. IoC Container

  11. IoC Container

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

  13. WHAT IS IOC? class Service { public function __construct() {

    init(); } public function init() { $config = config(‘ftpsettings…’); $this->ftp = new FTP(…); } public function calculate(); }
  14. WHAT IS IOC? $service = $container->get(Service::class); $service->calculate();

  15. But first: Design patterns WHAT IS IOC?

  16. class Service { public function init() { // setup FTP

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

    function calculate() { // Get stuff from FTP. // Do calculations. // Return result. } } WHAT IS IOC?
  18. 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?
  19. 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?
  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 // Log that FTP is accessed. // Do calculations. // Return result. } } WHAT IS IOC?
  21. class FtpStorage { public function __construct(Ftp $ftp) { $this->ftp =

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

    $storage; } public function calculate() { $value = $this->storage->get($key); return $calculate($value); } } WHAT IS IOC?
  23. interface Storage { public function get($key); } WHAT IS IOC?

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

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

    $storage; } } WHAT IS IOC?
  26. 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?
  27. 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?
  28. class FtpStorage implements Storage { public function __construct(Ftp $ftp) {

    $this->ftp = $ftp; } public function get($key) { // Get it and return it. } } WHAT IS IOC?
  29. FtpStorage Service depends on depends on Ftp WHAT IS IOC?

  30. FtpStorage implements <interface> Storage Service depends on depends on Ftp

    WHAT IS IOC?
  31. FtpStorage <interface> Storage CacheDecorator Service LogDecorator S3Storage WHAT IS IOC?

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

    depends on Ftp FtpStorage ervice depends on depends on Ftp
  33. new Service($ftp); WHAT IS IOC?

  34. new Service( new CacheDecorator( $cache, new LogDecorator( $logger, new FtpStorage(

    new Ftp($settings) ) ) ) ); WHAT IS IOC?
  35. $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?
  36. Dependency inversion: Interface + implementation Dependency injection: Injecting `Storage` into

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

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

    IOC?
  39. 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
  40. PSR-11 namespace Psr\Container; interface ContainerInterface { public function get($id); public

    function has($id); }
  41. None
  42. None
  43. None
  44. Automatic object resolution

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

  46. Reflection CONSTRUCTOR INJECTION

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

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

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

  50. Recursively CONSTRUCTOR INJECTION

  51. CONSTRUCTOR INJECTION SendInvoice ➡ Mailer ➡ SMTP

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

    - … CONSTRUCTOR INJECTION
  53. But what if our code is SOLID… The container can’t

    construct interfaces? CONSTRUCTOR INJECTION
  54. Helping the container

  55. $container->bind($key, $closure); REGISTRATION - BINDING

  56. $container->bind(Mailer::class, function () { // Create mailer… return $mailer; });

    REGISTRATION - BINDING
  57. Situation 1: When <<interface>> is type hinted, resolve <<class>> instead

    REGISTRATION - BINDING
  58. Solution: Aliasing. REGISTRATION - BINDING

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

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

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

    - BINDING
  62. Solution: Define explicitly. REGISTRATION - BINDING

  63. $container->bind(Mailer::class, function () { return new Mailer($username, $password); }); REGISTRATION

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

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

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

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

  68. $container->singleton(EntityManagerInterface::class, function () … }); $container->bind($key, $function, true); REGISTRATION -

    BINDING
  69. Situation 4: When different classes need a different object of

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

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

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

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

    }); REGISTRATION - BINDING
  74. Situation 5: When resolving <<interface>>, do some setup. REGISTRATION -

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

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

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

    - BINDING
  78. $container->resolving(function (EnvironmentAware $object) { $object->setEnv(app()->environment()); }); REGISTRATION - CONTAINER EVENTS

  79. Also useful for: - Quickly injecting stuff REGISTRATION - CONTAINER

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

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

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

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

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

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

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

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

  89. Service Providers! REGISTRATION

  90. REGISTRATION public function boot() { // Macros, extensions, container events,

    … } public function register() { // Bindings }
  91. REGISTRATION public function boot(View $view) { $view->composer(…); }

  92. REGISTRATION protected $defer = true; public function provides() { return

    [Connection::class]; } public function register() { $this->app->bind(Connection::class, …); }
  93. Using a container

  94. Retrieving objects CALLING THE CONTAINER

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

  96. Retrieving objects, with arguments CALLING THE CONTAINER

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

    folder to use. }); $container->get('fs', [‘images']); CALLING THE CONTAINER
  98. $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
  99. Calling functions, with resolved params CALLING THE CONTAINER

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

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

    THE CONTAINER
  102. (new ReflectionFunction($callback)) ->getParameters(); CALLING THE CONTAINER

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

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

    }; } CALLING THE CONTAINER
  105. Tagging CALLING THE CONTAINER

  106. $container->tag('fs.images', 'filesystem'); $container->tag('fs.pdfs', 'filesystem'); $filesystems = $container->tagged('filesystem'); foreach ($filesystems as

    $filesystem) { … } CALLING THE CONTAINER
  107. $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
  108. When to inject the container?

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

    Bus - Factories - … CALLING THE CONTAINER
  110. Bonus

  111. Solving circular dependencies BONUS - CIRCULAR DEPENDENCIES

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

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

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

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

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

  117. Thank you! @hannesvdvreken @laravelliveuk

  118. Time for questions. @hannesvdvreken @laravelliveuk