12. PROS
• Separation of concerns:
• dependency resolution, configuration and lifecycle
• Enforcing Single Responsibility Principle
• Easier testing
• Modular architecture, loose coupling
13. CONS
• Learning curve
• Code is harder do analyse/debug
• Moves complexity somewhere else (doesn’t remove)
• Need for extra tools like Containers / Dispatchers
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.”);
}
}
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. EXAMPLE WITH DI
$logger = new PrintingLogger();
$eraser = new LocalFileEraser($logger);
$eraser->erase('important-passwords.txt');
22. What (is being executed)
Known Unknown
KnownUnknown
When(isbeingexecuted)
Dependency
Injection
Stages of loosening control
(from the component point of view)
25. EXAMPLE WITH EVENTS
class FileEvent implements Event
{
private $path;
public function __construct($path)
{
$this->path = $path;
}
public function getPath()
{
return $this->path;
}
}
26. EXAMPLE WITH EVENTS
class FileEraseWasInitialised extends FileEvent
{
}
class FileWasErased extends FileEvent
{
}
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. 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. 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. EXAMPLE WITH EVENTS
$eraser = new LocalFileEraser();
$listener = new LoggingFileEventListener(new PrintingLogger());
$eraser->addListener($listener);
$eraser->erase('important-passwords.txt');
31. What (is being executed)
Known Unknown
KnownUnknown
When(isbeingexecuted)
Events
Stages of loosening control
(from the component point of view)
33. EXAMPLE WITH AOP USING DECORATOR
class LocalFileEraser implements FileEraser
{
public function erase($filename)
{
unlink($filename);
}
}
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. EXAMPLE WITH AOP USING DECORATOR
$localFileEraser = new LocalFileEraser();
$logger = new PrintingLogger();
$eraser = new LoggingFileEraser($localFileEraser, $logger);
$eraser->erase('important-passwords.txt');
36. What (is being executed)
Known Unknown
KnownUnknown
When(isbeingexecuted)
AOP
Stages of loosening control
(from the component point of view)
37. What (is being executed)
Known Unknown
KnownUnknown
When(isbeingexecuted)
Dependency
Injection
Events
AOP
Stages of loosening control
(from the component point of view)
55. SERVICE LOCATOR
• Coupled to container
• Responsible for resolving dependencies
• Dependencies are hidden
• Hard to test
• Might be ok when modernising legacy!
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. 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. 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. SETTER INJECTION
class NullLogger implements Logger {
public function log($log)
{
// whateva...
}
}
$eraser = new LocalFileEraser(new NullLogger());
$eraser->erase('important-passwords.txt');
62. DI WAY OF DOING THINGS
interface Logger
{
public function log($log);
}
class PrintingLogger implements Logger
{
public function log($log)
{
echo $log;
}
}
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. DI WAY OF DOING THINGS
$logger = new PrintingLogger();
$eraser = new LocalFileEraser($logger);
$eraser->erase('important-passwords.txt');
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. 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');
69. FEATURES
• Many configurations formats
• Supports Factories/Configurators/Scopes/Decoration
• Extendable with Compiler Passes
• Supports lazy loading of services
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
79. PRIVATE SERVICES
• It’s only a hint for compiler
• Minor performance gain (inlines instations)
• Private services can still be fetched (not recommended)
81. LAZY LOADING OF SERVICES
• Used when instantiation is expensive or not needed
• i.e. event listeners
• Solutions:
• Injecting container directly
• Using proxy objects
82. INJECTING CONTAINER DIRECTLY
• As fast as it can be
• Couples service implementation to the container
• Makes testing harder
83. 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
89. CIRCULAR REFERENCES
• Injecting Container is just a workaround
• Using setter injection after instantiation as well
• Solving design problem is the real challenge
95. 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
108. EXTENDING BASE CONTROLLER
• Easy to use by newcomers / low learning curve
• Limits inheritance
• Encourages using DIC as Service Locator
• Hard unit testing
109. CONTAINER AWARE INTERFACE
• Controller is still coupled to framework
• Lack of convenience methods
• Encourages using DIC as Service Locator
• Testing is still hard
110. 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
111. NONE OF THE ABOVE OPTIONS WILL
FORCE YOU TO WRITE GOOD/BAD CODE
113. FEATURES
• Implementation of Mediator pattern
• Allows for many-to-many relationships between objects
• Makes your projects extensible
• Supports priorities/stopping event flow
114. EVENT DISPATCHER
• Can be (really) hard to debug
• Priorities of events / managing event flow
• Events can be mutable - indirect coupling
• Hard to test
115. 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
116. 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
119. 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