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

How I learned to Stop Wiring and Love Autowirin...

How I learned to Stop Wiring and Love Autowiring Containers SymfonyCon Berlin 2016

Does managing YAML, XML or PHP container configurations make you sad? Do you dread making changes to your classes' dependencies for fear of the inevitable container configuration wiring blues? Life doesn't have to be this way! Not if your container supports autowiring, that is. Hear one developer's journey into the wild world of containers, learn how autowiring works, and find out how using autowiring can free you from having to manually configure every dependency.

Beau Simensen

December 02, 2016
Tweet

More Decks by Beau Simensen

Other Decks in Programming

Transcript

  1. How I learned to Stop Wiring and Love Autowiring Containers

    joind.in/talk/666c9 Beau Simensen • beau.io • @beausimensen
  2. Dependency Inversion .-- Dependency Injection --. | | Inversion of

    Control --+--------------------------+-- Container | | `-- Service Locator -------' Dependency Injection Container Inversion of Control Container Service Container
  3. Dependency Inversion .-- Dependency Injection --. | | Inversion of

    Control --+--------------------------+-- Container | | `-- Service Locator -------' Dependency Injection Container Inversion of Control Container Service Container Container
  4. Dependency Inversion .-- Dependency Injection --. | | Inversion of

    Control --+--------------------------+-- Container | | `-- Service Locator -------' Dependency Injection Container ## . Inversion of Control Container ## ## ## == Service Container ## ## ## ## === Container /""""""""""""""""\___/ === ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~ \______ o __/ (no, not that other type of container) \ \ __/ \____\______/
  5. services: app.word_list: class: 'AppBundle\Game\WordList' calls: - [ 'addWord', [ 'computer'

    ] ] - [ 'addWord', [ 'monitors' ] ] - [ 'addWord', [ 'cellular' ] ] public: false app.game_context: class: 'AppBundle\Game\GameContext' arguments: ['@session'] public: false app.game_runner: class: 'AppBundle\Game\GameRunner' arguments: ['@app.game_context', '@?app.word_list']
  6. public class RealBillingService implements BillingService { private final CreditCardProcessor processor;

    private final TransactionLog transactionLog; @Inject public RealBillingService(CreditCardProcessor processor, TransactionLog transactionLog) { this.processor = processor; this.transactionLog = transactionLog; } } Injector injector = Guice.createInjector(new BillingModule()); BillingService billingService = injector.getInstance( BillingService.class );
  7. That never happens but if it does there are ways.

    — Beau's entirely unconvincing friend
  8. $context->add('configuration', array( 'className' => 'dd_configuration_PropertiesConfiguration', 'constructorArgs' => array( 'locations' =>

    array( 'lithe_base.properties', 'app.properties', 'app.site.properties', ), ), )); $context->add('placeholderConfigurer', array( 'className' => 'substrate_DdConfigurationPlaceholderConfigurer', 'constructorArgs' => array( 'configuration' => $context->ref('configuration'), ), )); $context->add('logFactory', array( 'className' => 'dd_logging_LogFactory', ));
  9. foreach ($constructor->getParameters() as $reflectionParamter) { $constructorArgumentName = $reflectionParamter->getName(); $paramClass =

    $reflectionParamter->getClass(); if ($paramClass) { $paramClassName = $paramClass->getName(); foreach ($this->stoneInstances as $testStone) { if ($testStone instanceof $paramClassName) { $constructorArgs[] = $testStone; break; } } } }
  10. class dd_logging_FileLogger { public function __construct($filename) { /* .. */

    } } class dd_logging_LogFactory { public function __construct(dd_logging_FileLogger $logger) { /* .. */ } } $context->add('logger', array( 'className' => 'dd_logging_FileLogger', 'constructorArgs' => array( 'filename' => __DIR__.'/app.log', ), )); $context->add('logFactory', array( 'className' => 'dd_logging_LogFactory', ));
  11. class ImportantService { private $loggerFactory; public function __construct($loggerFactory) { $this->loggerFactory

    = $loggerFactory; } public function doImportantTask() { $this ->loggerFactory() ->getLogger() ->info('Did important task!'); } }
  12. # k.php class ImportantService { /* ... */ } $fileLogger

    = new FileLoger(__DIR__.'/app.log'); $loggerFactory = new LoggerFactory($fileLogger); $importantService = new ImportantService($loggerFactory); $importantService->doImportantTask();
  13. # services.yml services: fileLogger: class: 'FileLogger' arguments: ['%kernel.root_dir%/app.log'] loggerFactory: class:

    'LoggerFactory' arguments: ['@fileLogger'] importantService: class: 'ImportantService' arguments: ['@loggerFactory'] $importantService = $container->get('importantService'); $importantService->doImportantTask();
  14. <!-- app/config/services.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container> <services> <service

    id="fileLogger" class="FileLogger"> <argument>%kernel.root_dir%/app.log</argument> </service> <service id="loggerFactory" class="LoggerFactory"> <argument type="service" id="fileLogger" /> </service> <service id="importantService" class="ImportantService"> <argument type="service" id="loggerFactory" /> </service> </services> </container> $importantService = $container->get('importantService'); $importantService->doImportantTask();
  15. $container = new Pimple\Container(); $container['fileLogger'] = function () { return

    new FileLogger(__DIR__.'/app.log'); }; $container['loggerFactory'] = function ($c) { return new LoggerFactory($c['fileLogger']); }; $container['importantService'] = function ($c) { return new ImportantService($c['loggerFactory']); }; $importantService = $container['importantService']; $importantService->doImportantTask();
  16. $container = new Illuminate\Container\Container(); $container->singleton('fileLogger', function ($c) { return new

    FileLogger(__DIR__.'/app.log'); }); $container->singleton('loggerFactory', function ($c) { return new LoggerFactory($c->make('loggerFactory')); }); $container->singleton('importantService', function ($c) { return new ImportantService($c->make('loggerFactory')); }); $importantService = $container->make('importantService'); $importantService->doImportantTask();
  17. $container = new Illuminate\Container\Container(); $container->singleton(FileLogger::class, function ($c) { return new

    FileLogger(__DIR__.'/app.log'); }); $container->singleton(LoggerFactory::class, function ($c) { return new LoggerFactory($c->make(FileLogger::class)); }); $container->singleton(ImportantService::class, function ($c) { return new ImportantService($c->make(LoggerFactory::class)); }); $importantService = $container->make(ImportantService::class); $importantService->doImportantTask();
  18. foreach ($constructor->getParameters() as $reflectionParamter) { $constructorArgumentName = $reflectionParamter->getName(); $paramClass =

    $reflectionParamter->getClass(); if ($paramClass) { $paramClassName = $paramClass->getName(); foreach ($this->stoneInstances as $testStone) { if ($testStone instanceof $paramClassName) { $constructorArgs[] = $testStone; break; } } } }
  19. foreach ($constructor->getParameters() as $reflectionParamter) { $constructorArgumentName = $reflectionParamter->getName(); $paramClass =

    $reflectionParamter->getClass(); if ($paramClass) { $paramClassName = $paramClass->getName(); $found = false; foreach ($this->stoneInstances as $testStone) { if ($testStone instanceof $paramClassName) { $constructorArgs[] = $testStone; $found = true; break; } } if (! $found) { $constructorArgs[] = $this->make($paramClassName); } } }
  20. $container = new Illuminate\Container\Container(); $container->singleton(FileLogger::class, function ($c) { return new

    FileLogger(__DIR__.'/app.log'); }); $container->singleton(LoggerFactory::class, function ($c) { return new LoggerFactory($c->make(FileLogger::class)); }); $container->singleton(ImportantService::class, function ($c) { return new ImportantService($c->make(LoggerFactory::class)); }); $importantService = $container->make(ImportantService::class); $importantService->doImportantTask();
  21. $container = new Illuminate\Container\Container(); $container->singleton(FileLogger::class, function ($c) { return new

    FileLogger(__DIR__.'/app.log'); }); $container->singleton(LoggerFactory::class, function ($c) { return new LoggerFactory($c->make(FileLogger::class)); }); $importantService = $container->make(ImportantService::class); $importantService->doImportantTask();
  22. $container = new Illuminate\Container\Container(); $container->singleton(FileLogger::class, function ($c) { return new

    FileLogger(__DIR__.'/app.log'); }); $importantService = $container->make(ImportantService::class); $importantService->doImportantTask();
  23. class ImportantService { private $loggerFactory; public function __construct($loggerFactory) { $this->loggerFactory

    = $loggerFactory; } public function doImportantTask() { $this ->loggerFactory() ->getLogger() ->info('Did important task!'); } }
  24. class ImportantService { private $loggerFactory; private $connection; public function __construct($loggerFactory,

    $connection) { $this->loggerFactory = $loggerFactory; $this->connection = $connection; } public function doImportantTask() { $this->connection->execute(); $this ->loggerFactory() ->getLogger() ->info('Did important task!'); } }
  25. # k.php class Connection { /* ... */ } class

    ImportantService { /* ... */ } $fileLogger = new FileLoger(__DIR__.'/app.log'); $loggerFactory = new LoggerFactory($fileLogger); $connection = new Connection(); $importantService = new ImportantService( $loggerFactory, $connection ); $importantService->doImportantTask();
  26. # services.yml services: fileLogger: class: 'FileLogger' arguments: ['%kernel.root_dir%/app.log'] loggerFactory: class:

    'LoggerFactory' arguments: ['@fileLogger'] importantService: class: 'ImportantService' arguments: ['@loggerFactory'] $importantService = $container->get('importantService'); $importantService->doImportantTask();
  27. # services.yml services: fileLogger: class: 'FileLogger' arguments: ['%kernel.root_dir%/app.log'] loggerFactory: class:

    'LoggerFactory' arguments: ['@fileLogger'] connection: class: 'Connection' importantService: class: 'ImportantService' arguments: ['@loggerFactory', '@connection'] $importantService = $container->get('importantService'); $importantService->doImportantTask();
  28. <!-- app/config/services.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container> <services> <service

    id="fileLogger" class="FileLogger"> <argument>%kernel.root_dir%/app.log</argument> </service> <service id="loggerFactory" class="LoggerFactory"> <argument type="service" id="fileLogger" /> </service> <service id="importantService" class="ImportantService"> <argument type="service" id="loggerFactory" /> </service> </services> </container> $importantService = $container->get('importantService'); $importantService->doImportantTask();
  29. <!-- app/config/services.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container> <services> <service

    id="fileLogger" class="FileLogger"> <argument>%kernel.root_dir%/app.log</argument> </service> <service id="loggerFactory" class="LoggerFactory"> <argument type="service" id="fileLogger" /> </service> <service id="connection" class="Connection" /> <service id="importantService" class="ImportantService"> <argument type="service" id="loggerFactory" /> <argument type="service" id="connection" /> </service> </services> </container> $importantService = $container->get('importantService'); $importantService->doImportantTask();
  30. $container = new Pimple\Container(); $container['fileLogger'] = function () { return

    new FileLogger(__DIR__.'/app.log'); }; $container['loggerFactory'] = function ($c) { return new LoggerFactory($c['fileLogger']); }; $container['importantService'] = function ($c) { return new ImportantService($c['loggerFactory']); }; $importantService = $container['importantService']; $importantService->doImportantTask();
  31. $container = new Pimple\Container(); $container['fileLogger'] = function () { return

    new FileLogger(__DIR__.'/app.log'); }; $container['loggerFactory'] = function ($c) { return new LoggerFactory($c['fileLogger']); }; $container['connection'] = function ($c) { return new Connection(); }; $container['importantService'] = function ($c) { return new ImportantService( $c['loggerFactory'], $c['connection'] ); }; $importantService = $container['importantService']; $importantService->doImportantTask();
  32. It had major impact on design I didn't want to

    have to tackle configuration
  33. class LoggerFactory { public function __construct(FileLogger $fileLogger) { /* ...

    */ } } class FileLogger { public function info($message) { /* ... */ } }
  34. class LoggerFactory { public function __construct(Logger $logger) { /* ...

    */ } } interface Logger { public function info($message); } class FileLogger implements Logger { public function info($message) { /* ... */ } }
  35. # k.php class Connection { /* ... */ } class

    ImportantService { /* ... */ } class UnimportantService { /* ... */ } $fileLogger = new FileLoger(__DIR__.'/app.log'); $fileLoggerFactory = new LoggerFactory($fileLogger); $connection = new Connection(); $importantService = new ImportantService( $fileLoggerFactory, $connection ); $nullLogger = new NullLogger(); $nullLoggerFactory = new LoggerFactory($nullLogger) $unimportantService = new UnimportantService( $nullLoggerFactory );
  36. $container = new Illuminate\Container\Container(); $container ->when(FileLogger::class) ->needs('$filename') ->give(__DIR__.'/app.log'); $container->bind(Logger::class, FileLogger::class);

    $importantService = $container->make(ImportantService::class); $unimportantService = $container->make(UnimportantService::class);
  37. $container = new Illuminate\Container\Container(); $container ->when(FileLogger::class) ->needs('$filename') ->give(__DIR__.'/app.log'); $container->bind(Logger::class, FileLogger::class);

    $container ->when(UnimportantService::class) ->needs(LoggerFactory::class) ->give(function ($c) { $nullLogger = $c->make(NullLogger::class); return new LoggerFactory($nullLogger); }); $importantService = $container->make(ImportantService::class); $unimportantService = $container->make(UnimportantService::class);
  38. class Homepage { private $router; private $twig; public function __construct(RouterInterface

    $router, \Twig_Environment $twig) { $this->router = $router; $this->twig = $twig; } /** * @Route("/myaction", name="my_action") */ public function __invoke(Request $request) { if (!$request->isMethod('GET')) { return new RedirectResponse($this->router->generateUrl('my_action'), 301); } return new Response($this->twig->render('mytemplate.html.twig')); } }