Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

The IoC Hydra - Dutch PHP Conference 2016

898 views

Published on

Slides from my talk presented during Dutch PHP Conference in Amsterdam - 25 June 2016

Published in: Technology
  • Be the first to comment

The IoC Hydra - Dutch PHP Conference 2016

  1. 1. The IoC Hydra
  2. 2. Kacper Gunia @cakper Independent Consultant & Trainer DDD London Leader @ddd_london
  3. 3. CHAPTER 1: THE THEORY
  4. 4. THIS IS NOT YET ANOTHER DEPENDENCY INJECTION TALK ;)
  5. 5. SO WE NEED SOME CLARIFICATION
  6. 6. IOC !== DI
  7. 7. –Pico Container “Inversion of Control (IoC) is a design pattern that addresses a component’s dependency resolution, configuration and lifecycle. ”
  8. 8. –Pico Container “It suggests that the control of those three should not be the concern of the component itself. Thus it is inverted back.”
  9. 9. –Pico Container “Dependency Injection is where components are given their dependencies through their constructors, methods, or directly into fields.”
  10. 10. Inversion of Control Dependency Injection Events AOP
  11. 11. WHY DO WE IOC?
  12. 12. PROS • Separation of concerns: • dependency resolution, configuration and lifecycle • Enforcing Single Responsibility Principle • Easier testing • Modular architecture, loose coupling
  13. 13. CONS • Learning curve • Code is harder do analyse/debug • Moves complexity somewhere else (doesn’t remove) • Need for extra tools like Containers / Dispatchers
  14. 14. HOW DO WE WRITE SOFTWARE WITHOUT IOC?
  15. 15. EXAMPLE interface FileEraser
 {
 public function erase($filename);
 }
  16. 16. EXAMPLE interface Logger
 {
 public function log($log);
 }
 
 class PrintingLogger implements Logger
 {
 public function log($log)
 {
 echo $log;
 }
 }
  17. 17. EXAMPLE class LocalFileEraser implements FileEraser
 {
 public function erase($filename) {
 $logger = new PrintingLogger();
 $logger->log("Attempt to erase file: " . $filename);
 
 unlink($filename);
 
 $logger->log("File " . $filename . " was erased.”);
 }
 }
  18. 18. EXAMPLE $eraser = new LocalFileEraser();
 $eraser->erase('important-passwords.txt');
  19. 19. HOW CAN WE FIX IT WITH DI?
  20. 20. EXAMPLE WITH DI class LocalFileEraser implements FileEraser {
 private $logger;
 
 public function __construct(Logger $logger) {
 $this->logger = $logger;
 }
 
 public function erase($path) {
 $this->logger->log("Attempt to erase file: " . $path); 
 unlink($path); 
 $this->logger->log("File " . $path . " was erased.");
 }
 }
  21. 21. EXAMPLE WITH DI $logger = new PrintingLogger();
 $eraser = new LocalFileEraser($logger);
 
 $eraser->erase('important-passwords.txt');
  22. 22. What (is being executed) Known Unknown KnownUnknown When(isbeingexecuted) Dependency Injection Stages of loosening control (from the component point of view)
  23. 23. HOW CAN WE FIX IT WITH EVENTS?
  24. 24. EXAMPLE WITH EVENTS class FileEvent implements Event
 {
 private $path;
 public function __construct($path)
 {
 $this->path = $path;
 }
 
 public function getPath()
 {
 return $this->path;
 }
 }
  25. 25. EXAMPLE WITH EVENTS class FileEraseWasInitialised extends FileEvent
 {
 
 }
 
 class FileWasErased extends FileEvent
 {
 
 }
  26. 26. EXAMPLE WITH EVENTS interface Listener
 {
 public function handle(Event $event);
 }
 
 interface Event
 {
 
 }
  27. 27. EXAMPLE WITH EVENTS class LoggingFileEventListener implements Listener
 {
 private $logger;
 
 public function __construct(Logger $logger)
 {
 $this->logger = $logger;
 }
 
 public function handle(Event $event)
 {
 if ($event instanceof FileEvent) {
 $this->logger->log(get_class($event).' '.$event->getPath());
 }
 }
 }
  28. 28. EXAMPLE WITH EVENTS trait Observable
 {
 private $listeners = [];
 
 public function addListener(Listener $listener)
 {
 $this->listeners[] = $listener;
 }
 
 public function dispatch(Event $event)
 {
 foreach ($this->listeners as $listener) {
 $listener->handle($event);
 }
 }
 }
  29. 29. EXAMPLE WITH EVENTS class LocalFileEraser implements FileEraser
 {
 use Observable;
 
 public function erase($filename)
 {
 $this->dispatch(new FileEraseWasInitialised($filename));
 
 unlink($filename);
 
 $this->dispatch(new FileWasErased($filename));
 }
 }
  30. 30. EXAMPLE WITH EVENTS $eraser = new LocalFileEraser();
 $listener = new LoggingFileEventListener(new PrintingLogger()); $eraser->addListener($listener);
 
 $eraser->erase('important-passwords.txt');
  31. 31. What (is being executed) Known Unknown KnownUnknown When(isbeingexecuted) Events Stages of loosening control (from the component point of view)
  32. 32. HOW CAN WE FIX IT WITH AOP?
  33. 33. EXAMPLE WITH AOP USING DECORATOR class LocalFileEraser implements FileEraser
 {
 public function erase($filename)
 {
 unlink($filename);
 }
 }
  34. 34. EXAMPLE WITH AOP USING DECORATOR class LoggingFileEraser implements FileEraser
 {
 private $decorated;
 
 private $logger;
 
 public function __construct(FileEraser $decorated, Logger $logger)
 {
 $this->decorated = $decorated;
 $this->logger = $logger;
 }
 
 public function erase($filename)
 {
 $this->logger->log('File erase was initialised' . $filename);
 
 $this->decorated->erase($filename);
 
 $this->logger->log('File was erased' . $filename);
 }
 }
  35. 35. EXAMPLE WITH AOP USING DECORATOR $localFileEraser = new LocalFileEraser();
 $logger = new PrintingLogger();
 
 $eraser = new LoggingFileEraser($localFileEraser, $logger);
 
 $eraser->erase('important-passwords.txt');
  36. 36. What (is being executed) Known Unknown KnownUnknown When(isbeingexecuted) AOP Stages of loosening control (from the component point of view)
  37. 37. What (is being executed) Known Unknown KnownUnknown When(isbeingexecuted) Dependency Injection Events AOP Stages of loosening control (from the component point of view)
  38. 38. FRAMEWORKS & LIBRARIES
  39. 39. A libraries provide functionality that you decide when to call.
  40. 40. Frameworks provide an architecture for the application and decide when to call your code.
  41. 41. “DON’T CALL US, WE’LL CALL YOU” aka Hollywood Principle
  42. 42. Frameworks utilise IoC principles and can be seen as one of its manifestations.
  43. 43. CHAPTER 2: THE PRACTICE
  44. 44. DEPENDENCY INJECTION CONTAINERS
  45. 45. WHERE DIC CAN HELP
  46. 46. RESOLVING GRAPHS OF OBJECTS $dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8';
 $dbUsername = 'username';
 $dbPassword = 'password';
 $customerRepository = new CustomerRepository(new PDO($dsn, $dbUsername, $dbPassword));
 
 $smtpHost = 'smtp.example.org';
 $smtpPort = 25;
 $smtpUsername = 'username';
 $smtpPassword = 'password';
 $transport = new Swift_SmtpTransport($smtpHost, $smtpPort);
 $transport->setUsername($smtpUsername);
 $transport->setPassword($smtpPassword);
 $mailer = new Swift_Mailer($transport);
 
 $loggerName = "App";
 $logger = new MonologLogger($loggerName);
 $stream = "app.log";
 $logger->pushHandler(new MonologHandlerStreamHandler($stream));
 
 $controller = new RegistrationController($customerRepository, $mailer, $logger);
  47. 47. RESOLVING GRAPHS OF OBJECTS use PimpleContainer;
 
 $container = new Container();
 
 $container['dsn'] = 'mysql:host=localhost;dbname=testdb;charset=utf8';
 $container['db_username'] = 'username';
 $container['dsn'] = 'password';
 
 $container['pdo'] = function ($c) {
 return new PDO($c['dsn'], $c['db_username'], $c['dsn']);
 }; 
 $container['customer_repository'] = function ($c) {
 return new CustomerRepository($c['pdo']);
 };
  48. 48. RESOLVING GRAPHS OF OBJECTS $container['smtp_host'] = 'smtp.example.org';
 $container['smtp_port'] = 25;
 $container['smtp_username'] = 'username';
 $container['smtp_password'] = 'password';
 
 $container['transport'] = function ($c) {
 return new Swift_SmtpTransport($c['smtp_host'], $c['smtp_port']);
 };
 
 $container->extend('transport', function ($transport, $c) {
 $transport->setUsername($c['smtp_username']);
 $transport->setPassword($c['smtp_password']);
 
 return $transport;
 });
 
 $container['mailer'] = function ($c) {
 return new Swift_Mailer($c['transport']);
 };
  49. 49. RESOLVING GRAPHS OF OBJECTS $container['logger_name'] = "App";
 $container['stream_name'] = "app_" . $container['environment'] . ".log";
 
 $container['logger'] = function ($c) {
 return new MonologLogger($c['logger_name']);
 };
 
 $container->extend('transport', function ($logger, $c) {
 $logger->pushHandler( new MonologHandlerStreamHandler($c[‘stream_handler']) );
 
 return $logger;
 });
 
 $container['stream_handler'] = function ($c) {
 return new MonologHandlerStreamHandler($c['stream_name']);
 };
  50. 50. RESOLVING GRAPHS OF OBJECTS $container['registration_controller'] = function ($c) {
 return new RegistrationController( $c['customer_repository'], $c['mailer'], $c[‘logger’] );
 };
  51. 51. LIFECYCLE MANAGEMENT $container['session_storage'] = function ($c) {
 return new SessionStorage('SESSION_ID');
 };
  52. 52. LIFECYCLE MANAGEMENT $container['session_storage'] = $container->factory(function ($c) {
 return new SessionStorage('SESSION_ID');
 });
  53. 53. WHERE DIC CAN HARM
  54. 54. SERVICE LOCATOR class RegistrationController {
 function resetPasswordAction() {
 $mailer = Container::instance()['mailer'];
 //...
 }
 }
  55. 55. SERVICE LOCATOR • Coupled to container • Responsible for resolving dependencies • Dependencies are hidden • Hard to test • Might be ok when modernising legacy!
  56. 56. SETTER INJECTION
  57. 57. SETTER INJECTION • Forces to be defensive as dependencies are optional • Dependency is not locked (mutable) • In some cases can be replaced with events • We can avoid it by using NullObject pattern
  58. 58. SETTER INJECTION class LocalFileEraser implements FileEraser
 {
 private $logger;
 
 public function setLogger(Logger $logger)
 {
 $this->logger = $logger;
 }
 
 public function erase($filename)
 {
 if ($this->logger instanceof Logger) {
 $this->logger->log("Attempt to erase file: " . $filename);
 }
 
 unlink($filename);
 
 if ($this->logger instanceof Logger) {
 $this->logger->log("File " . $filename . " was deleted");
 }
 }
 }
  59. 59. SETTER INJECTION class LocalFileEraser implements FileEraser
 {
 private $logger;
 
 public function __construct(Logger $logger)
 {
 $this->logger = $logger;
 }
 
 public function erase($path)
 {
 $this->logger->log("Attempt to erase file: " . $path);
 
 unlink($path);
 
 $this->logger->log("File " . $path . " was erased.”);
 }
 }
  60. 60. SETTER INJECTION class NullLogger implements Logger {
 
 public function log($log)
 {
 // whateva...
 }
 }
 
 $eraser = new LocalFileEraser(new NullLogger());
 $eraser->erase('important-passwords.txt');
  61. 61. PARTIAL APPLICATION
  62. 62. DI WAY OF DOING THINGS interface Logger
 {
 public function log($log);
 }
 
 class PrintingLogger implements Logger
 {
 public function log($log)
 {
 echo $log;
 }
 }
  63. 63. DI WAY OF DOING THINGS class LocalFileEraser implements FileEraser
 {
 private $logger;
 
 public function __construct(Logger $logger)
 {
 $this->logger = $logger;
 }
 
 public function erase($path)
 {
 $this->logger->log("Attempt to erase file: " . $path);
 
 unlink($path);
 
 $this->logger->log("File " . $path . " was deleted");
 }
 }
  64. 64. DI WAY OF DOING THINGS $logger = new PrintingLogger();
 $eraser = new LocalFileEraser($logger);
 
 $eraser->erase('important-passwords.txt');
  65. 65. FUNCTIONAL WAY OF DOING THINGS $erase = function (Logger $logger, $path)
 {
 $logger->log("Attempt to erase file: " . $path);
 unlink($path);
 $logger->log("File " . $path . " was deleted");
 };
  66. 66. FUNCTIONAL WAY OF DOING THINGS use ReactPartial; 
 $erase = function (Logger $logger, $path)
 {
 $logger->log("Attempt to erase file: " . $path);
 unlink($path);
 $logger->log("File " . $path . " was deleted");
 };
 
 $erase = Partialbind($erase, new PrintingLogger()); 
 $erase('important-passwords.txt');
  67. 67. CHAPTER 3: THE SYMFONY
  68. 68. SYMFONY/DEPENDENCY-INJECTION
  69. 69. FEATURES • Many configurations formats • Supports Factories/Configurators/Decoration • Extendable with Compiler Passes • Supports lazy loading of services
  70. 70. PERFORMANCE
  71. 71. PERFORMANCE OF SYMFONY DIC • Cached/Dumped to PHP code • In debug mode it checks whether config is fresh • During Compilation phase container is being optimised
  72. 72. LARGE NUMBER OF CONFIGURATION FILES SLOWS CONTAINER BUILDER
  73. 73. SLOW COMPILATION • Minimise number of bundles/config files used • Try to avoid using extras like JMSDiExtraBundle • Can be really painful on NFS
  74. 74. LARGE CONTAINERS ARE SLOW TO LOAD AND USE A LOT OF MEMORY
  75. 75. LARGE CONTAINERS • Review and remove unnecessary services/bundles etc. • Split application into smaller ones with separate kernels
  76. 76. PRIVATE SERVICES
  77. 77. PRIVATE SERVICES • It’s only a hint for compiler • Minor performance gain (inlines instantiation) • Private services can still be fetched (not recommended)
  78. 78. LAZY LOADING OF SERVICES
  79. 79. LAZY LOADING OF SERVICES • Used when instantiation is expensive or not needed • i.e. event listeners • Solutions: • Injecting container directly • Using proxy objects
  80. 80. INJECTING CONTAINER DIRECTLY • As fast as it can be • Couples service implementation to the container • Makes testing harder
  81. 81. USING PROXY OBJECTS • Easy to use (just configuration option) • Code and test are not affected • Adds a bit of overhead • especially when services are called many times • on proxy generation
  82. 82. DEFINING CLASS NAMES AS PARAMETERS
  83. 83. DEFINING CLASS NAMES AS PARAMETERS • Rare use case (since decoration is supported even less) • Adds overhead • Removed in Symfony 3.0
  84. 84. CIRCULAR REFERENCES
  85. 85. Security Listener Doctrine CIRCULAR REFERENCE
  86. 86. CIRCULAR REFERENCES • Injecting Container is just a workaround • Using setter injection after instantiation as well • Solving design problem is the real challenge
  87. 87. Doctrine Security Listener TokenStorage BROKEN CIRCULAR REFERENCE
  88. 88. SCOPES
  89. 89. MEANT TO SOLVE “THE REQUEST PROBLEM”
  90. 90. SCOPE DEFINES STATE OF THE APPLICATION
  91. 91. PROBLEMS WITH INJECTING REQUEST TO SERVICES
  92. 92. PROBLEMS WITH INJECTING REQUEST TO SERVICES • Causes ScopeWideningInjectionException • Anti-pattern - Request is a Value Object • Which means Container was managing it’s state • Replaced with RequestStack
  93. 93. App Controller A Sub Request Stateful service Sub Request Master Request Stateful service Master Request Controller B REQUESTS MANAGED WITH SCOPES
  94. 94. App Controller A Stateless service Request Stack Controller B Sub Request Master Request REQUESTS MANAGED WITH STACK
  95. 95. FETCHING THE REQUEST FROM THE STACK IS AN ANTI PATTERN
  96. 96. REQUEST INSTANCE SHOULD BE PASSED AROUND
  97. 97. STATEFUL SERVICES
  98. 98. STATEFUL SERVICES • Refactor & pass the state to the call • Fetch state explicitly on per need basis • Use shared = false flag
  99. 99. PROTOTYPE SCOPE
  100. 100. Prototype scope Prototype- scoped Service Z Stateless Service A Prototype scope Prototype- scoped Service Z Stateless Service A Prototype scope Prototype- scoped Service Z Stateless Service A USING PROTOTYPE SCOPE WITH STRICT = TRUE
  101. 101. New Instances Stateful Service Z Stateful Service Z Stateful Service Z Stateless Service A USING PROTOTYPE SCOPE WITH STRICT = FALSE
  102. 102. FORGET ABOUT SCOPES ANYWAY WERE REMOVED IN SYMFONY 3.0
  103. 103. USE SHARED=FALSE FLAG
  104. 104. CONTROLLERS
  105. 105. EXTENDING BASE CONTROLLER • Easy to use by newcomers / low learning curve • Limits inheritance • Encourages using DIC as Service Locator • Hard unit testing
  106. 106. CONTAINER AWARE INTERFACE • Controller is still coupled to framework • Enables inheritance • Lack of convenience methods • Encourages using DIC as Service Locator • Testing is still hard
  107. 107. CONTROLLER AS A SERVICE • Requires additional configuration • Lack of convenience methods • Full possibility to inject only relevant dependencies • Unit testing is easy • Enables Framework-agnostic controllers
  108. 108. NONE OF THE ABOVE OPTIONS WILL FORCE YOU TO WRITE GOOD/BAD CODE
  109. 109. SYMFONY/EVENT-DISPATCHER
  110. 110. FEATURES • Implementation of Mediator pattern • Allows for many-to-many relationships between objects • Makes your projects extensible • Supports priorities/stopping event flow
  111. 111. EVENT DISPATCHER • Can be (really) hard to debug • Priorities of events / managing event flow • Events can be mutable - indirect coupling • Hard to test
  112. 112. INDIRECT COUPLING PROBLEM • Two services listening on kernel.request event: • Priority 16 - GeoIP detector - sets country code • Priority 8 - Locale detector - uses country code and user agent
  113. 113. INDIRECT COUPLING PROBLEM • Both events indirectly coupled (via Request->attributes) • Configuration change will change the app logic • In reality we always want to call one after another
  114. 114. WHEN TO USE EVENT DISPATCHER • Need to extend Framework or other Bundle • Building reusable Bundle & need to add extension points • Consider using separate dispatcher for Domain events
  115. 115. CHAPTER 4: THE END ;)
  116. 116. BE PRAGMATIC
  117. 117. BE EXPLICIT
  118. 118. DON’T RELY ON MAGIC
  119. 119. Kacper Gunia @cakper Independent Consultant & Trainer DDD London Leader Thanks! joind.in/talk/f681d
  120. 120. REFERENCES • http://martinfowler.com/bliki/InversionOfControl.html • http://picocontainer.com/introduction.html • http://www.infoq.com/presentations/8-lines-code-refactoring • http://richardmiller.co.uk/2014/03/12/avoiding-setter-injection/ • http://art-of-software.blogspot.co.uk/2013/02/cztery-smaki- odwracania-i-utraty.html

×