Pro Yearly is on sale from $80 to $50! »

IoC container - PHP CE 2017

IoC container - PHP CE 2017

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

November 04, 2017
Tweet

Transcript

  1. IOC container @hannesvdvreken PHPCE

  2. Hi, my name is Hannes.

  3. !

  4. None
  5. None
  6. IoC Container

  7. I O C

  8. International O C

  9. International Olympic C

  10. International Olympic Committee

  11. International Olympic Committee

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

  13. But first: Design patterns WHAT IS IOC?

  14. class Service { public function calculate() { // Get stuff

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

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

    $storage; } } WHAT IS IOC?
  21. interface Storage { public function get($key); } WHAT IS IOC?

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

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

    $storage; } } WHAT IS IOC?
  24. class CacheDecorator implements Storage { public function __construct(Cache $cache, Storage

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

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

    $this->ftp = $ftp; } public function get($key) { // Get it and return it. } } WHAT IS IOC?
  27. Dependency inversion: Interface + implementation Dependency injection: Injecting `Storage` into

    `Service` Inversion of Control: The container builds your objects and calls them WHAT IS IOC?
  28. FtpStorage Service depends on depends on Ftp WHAT IS IOC?

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

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

    IS IOC? implements
  31. new Service(new FtpStorage(new Ftp($settings))); WHAT IS IOC?

  32. Don’t create these instances yourself WHAT IS IOC?

  33. $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?
  34. Inversion of Control IoC container to the rescue WHAT IS

    IOC?
  35. IoC container is here to help you with Composition WHAT

    IS IOC?
  36. symfony/dependency-injection illuminate/container aura/di zendframework/zend-di pimple/pimple league/container … container-interop/container-interop psr/container (PSR-11)

    CONSTRUCTOR INJECTION
  37. None
  38. CONSTRUCTOR INJECTION namespace Interop\Container; use Psr\Container\ContainerInterface as PsrContainer; interface ContainerInterface

    extends PsrContainer { … }
  39. Automatic object resolution

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

    $mailer; } public function listen($event) { … } } CONSTRUCTOR INJECTION
  41. $listener = $container->get(SendInvoice::class); CONSTRUCTOR INJECTION

  42. Constructor injection CONSTRUCTOR INJECTION

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

  44. // League $container = new Container(); $container->delegate(new ReflectionContainer()); $listener =

    $container->get(SendInvoice::class); CONSTRUCTOR INJECTION
  45. Reflection CONSTRUCTOR INJECTION

  46. (new \ReflectionClass($class)) ->getConstructor() ->getParameters(); CONSTRUCTOR INJECTION

  47. Recursively CONSTRUCTOR INJECTION

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

    $mailer; } public function listen(InvoiceGeneratedEvent $event) { … } } CONSTRUCTOR INJECTION
  49. Auto wiring: - Symfony - Zend - Laravel - league/container

    - … CONSTRUCTOR INJECTION
  50. Helping the container

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

    REGISTRATION - BINDING
  52. Solution: Aliasing. REGISTRATION - BINDING

  53. // Symfony $container->setAlias(MailerInterface::class, Mailer::class); // Laravel $container->alias(MailerInterface::class, Mailer::class); // League,

    et al. $container->add(MailerInterface::class, function () { return $container->get(Mailer::class); }); REGISTRATION - BINDING
  54. Situation 2: When <<class>> has non-type hinted constructor arguments. REGISTRATION

    - BINDING
  55. Solution: Define dependencies. REGISTRATION - BINDING

  56. // Symfony $container->register(Mailer::class) ->addArgument(…); REGISTRATION - BINDING

  57. // Pimple $container[MailerInterface::class] = function () { … }; REGISTRATION

    - BINDING
  58. Situation 3: When <<class>> is type hinted, always return the

    same object. REGISTRATION - BINDING
  59. Example: Global state in a - Translator - View -

    Session REGISTRATION - BINDING
  60. Solution: Sharing an instance. REGISTRATION - BINDING

  61. // League $container->share(EntityManagerInterface::class, function () { … }); // Laravel

    $container->singleton(EntityManagerInterface::class, function () { … }); // Symfony $container->register()->setShared(true); REGISTRATION - BINDING
  62. Situation 4: When different classes need a different object of

    the same interface. REGISTRATION - BINDING
  63. Solution: Contextual binding. REGISTRATION - BINDING

  64. // Laravel $container->bind('fs.photos', function () {}); $container->bind('fs.files', function () {});

    REGISTRATION - BINDING
  65. // Laravel $container ->when(PhotosController::class) ->needs(FilesystemInterface::class) ->give('fs.photos'); REGISTRATION - CONTEXTUAL BINDING

  66. Situation 5: When resolving <<instance>>, do some setup. REGISTRATION -

    BINDING
  67. Example: When resolving the EntityManager, register some ModelListeners. REGISTRATION -

    BINDING
  68. Solution: Container events / inflection / hooks / … REGISTRATION

    - BINDING
  69. // League $container->inflector(EntityManager, function ($em) $em->getConfiguration() ->getEntityListenerResolver() ->register(new ModelListener()); });

    REGISTRATION - BINDING
  70. // Laravel $container->resolving(function (EntityManager $em) … }); REGISTRATION - BINDING

  71. interface EnvironmentAware { public function setEnv($env); } REGISTRATION - CONTAINER

    EVENTS
  72. trait EnvironmentAware { public function setEnv($env) { $this->env = $env;

    } private function isEnv($envs): bool { return in_array($this->env, (array) $envs); } } REGISTRATION - CONTAINER EVENTS
  73. $container->resolving(function (EnvironmentAware $object) { $object->setEnv(getenv(‘APP_ENV’)); }); REGISTRATION - CONTAINER EVENTS

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

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

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

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

    dependencies REGISTRATION - CONTAINER EVENTS
  78. $this->app->resolving(function (Mailer $mailer) { $mailer->setQueueResolver(function () { return $this->app->make(Queue::class); });

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

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

    dependencies - Setting configurable values REGISTRATION - CONTAINER EVENTS
  81. $this->app->resolving(function (Validator) { $fileTypes = $app['config']->get(…) $validator->setAllowedFileTypes($fileTypes); ); REGISTRATION -

    CONTAINER EVENTS
  82. Where to put all this? REGISTRATION

  83. container-interop/service-provider Similar to: - league/container - illuminate/container REGISTRATION

  84. Symfony: - Configuration files - Factories REGISTRATION

  85. Using a container

  86. Preferably only in - Factories - Event Dispatchers - Router

    - Command Bus - … CALLING THE CONTAINER
  87. Retrieving objects CALLING THE CONTAINER

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

  89. Retrieving objects, with arguments CALLING THE CONTAINER

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

    see what folder to use. }); $container->make('fs', ['images']); CALLING THE CONTAINER
  91. // Laravel $container->bind('fs.images', function () { return $container->make('fs', ['images']); });

    CALLING THE CONTAINER
  92. Calling functions, with resolved params CALLING THE CONTAINER

  93. // League & Laravel $container->call($closure); $container->call(function (Mailer $mailer) {}); $container->call([$listener,

    'handle']); $container->call('Listener::handle'); CALLING THE CONTAINER
  94. (new ReflectionFunction($callback)) ->getParameters(); CALLING THE CONTAINER

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

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

    } CALLING THE CONTAINER
  97. Tagging CALLING THE CONTAINER

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

    ($filesystems as $filesystem) { … } CALLING THE CONTAINER
  99. Bonus

  100. Solving circular dependencies BONUS - CIRCULAR DEPENDENCIES

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

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

    BONUS CIRCULAR DEPENDENCIES
  103. $queue = $container->get(QueueInterface); // League $container->inflector(Mailer::class, function () { $mailer->setQueueResolver(function

    () use ($container) return $container->get(QueueInterface::class); }); }); BONUS CIRCULAR DEPENDENCIES
  104. recap: Reflection Binding Calling code Bonus

  105. @hannesvdvreken Thank you! PHPCE

  106. @hannesvdvreken Time for questions. PHPCE

  107. • http://mwl.be REFERENCES