IoC container - PHP UK 2018

IoC container - PHP 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

February 16, 2018
Tweet

Transcript

  1. IOC container @hannesvdvreken #phpuk18

  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. IoC Container

  13. IoC Container

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

  15. But first: Design patterns WHAT IS IOC?

  16. class Service { 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

    $storage) {…} public function get($key) { return $this->cache->remember($key, 60, function () { return $this->storage->get($key); }); } } WHAT IS IOC?
  27. 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?
  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 Service depends on depends on Ftp

    FtpStorage implements <interface> Storage Service depends on depends on Ftp
  33. Dependency inversion: Interface + implementation Dependency injection: Injecting `Storage` into

    `Service` Inversion of Control: The container builds your objects and calls them WHAT IS IOC?
  34. new Service($ftp); WHAT IS IOC?

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

    new Ftp($settings) ) ) ) ); WHAT IS IOC?
  36. $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?
  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 … container-interop/container-interop psr/container (PSR-11)

    CONSTRUCTOR INJECTION
  40. None
  41. None
  42. CONSTRUCTOR INJECTION namespace Interop\Container; use Psr\Container\ContainerInterface as PsrContainer; interface ContainerInterface

    extends PsrContainer { … }
  43. CONSTRUCTOR INJECTION namespace Psr\Container; interface ContainerInterface { public function get($id);

    public function has($id); }
  44. Automatic object resolution

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

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

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

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

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

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

  51. Recursively CONSTRUCTOR INJECTION

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

    - … CONSTRUCTOR INJECTION
  53. Helping the container

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

    REGISTRATION - BINDING
  55. Solution: Aliasing. REGISTRATION - BINDING

  56. // 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
  57. Situation 2: When <<class>> has non-type hinted constructor arguments. REGISTRATION

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

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

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

    League $container->add(MailerInterface::class, function () { … }); REGISTRATION - BINDING
  61. Situation 3: When <<class>> is type hinted, always return the

    exact same object. REGISTRATION - BINDING
  62. Example: Global state in a - Translator - Entity Manager

    - Event dispatcher REGISTRATION - BINDING
  63. Solution: Sharing an instance. REGISTRATION - BINDING

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

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

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

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

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

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

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

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

    - BINDING
  72. // League $container->inflector(EntityManager::class, function (EntityManager $em) { $em->getConfiguration() ->getEntityListenerResolver() ->register(new

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

  74. interface EnvironmentAware { public function setEnv($env); } trait EnvironmentAware {

    public function setEnv($env) { $this->env = $env; } } REGISTRATION - CONTAINER EVENTS
  75. $container->resolving(function (EnvironmentAware $object) { $object->setEnv(getenv('APP_ENV')); }); REGISTRATION - CONTAINER EVENTS

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

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

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

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

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

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

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

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

    CONTAINER EVENTS
  84. Where to put all this? REGISTRATION

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

  86. Symfony: - Configuration files - Factories REGISTRATION

  87. Using a container

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

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

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

  91. Retrieving objects, with arguments CALLING THE CONTAINER

  92. // League $container->add('fs', function ($args) { // Use $args[0] to

    see what folder to use. }); $container->get('fs', ['images']); CALLING THE CONTAINER
  93. // League $container->add('fs.images', function () { return $container->get('fs', ['images']); });

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

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

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

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

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

    } CALLING THE CONTAINER
  99. Tagging CALLING THE CONTAINER

  100. Beware: Tagging in Symfony != Tagging in Laravel CALLING THE

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

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

  103. Solving circular dependencies BONUS - CIRCULAR DEPENDENCIES

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

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

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

    () { return $container->get(QueueInterface::class); }); }); BONUS CIRCULAR DEPENDENCIES
  107. recap: Reflection Aliasing Soft dependencies Using container

  108. Thank you! @hannesvdvreken #phpuk18

  109. Time for questions. @hannesvdvreken #phpuk18

  110. None
  111. • https://getstream.io/try-the-api REFERENCES