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

Learn to Stop Wiring and Love Laravel's Contain...

Learn to Stop Wiring and Love Laravel's Container TrueNorthPHP 2016

You've heard about dependency injection and inversion of control. Everything seems easy at first and you've found a container or two to help make your life easier. Until it isn't anymore. Suddenly you've found yourself managing complicated YAML, XML, or PHP container configurations. Making any change to your classes dependencies seems like a chore and any time you add a new class to the system you dread the inevitable configuration container configuration wiring blues.

Life doesn't have to be this way! In fact, life isn't this way for anyone who uses an autowiring container like Laravel's. Far from the most publicly marketed component, Illuminate\Container handles a lot of the magic that makes Laravel so much fun to use. Find out how you can use Laravel's container in almost any project! See how autowiring can free your mind from having to manually configure ever little dependency. Learn how you, too, can learn to stop wiring your dependency injection container and love Laravel's container!

Beau Simensen

November 05, 2016
Tweet

More Decks by Beau Simensen

Other Decks in Programming

Transcript

  1. Dependency Inversion .-- Dependency Injection --. | | Inversion of

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

    Control --+--------------------------+-- Container | | `-- Service Locator -------' Dependency Injection Container Inversion of Control Container Service Container Container
  3. 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) \ \ __/ \____\______/
  4. 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']
  5. 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 );
  6. That never happens but if it does there are ways.

    — Beau's entirely unconvincing friend
  7. $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', ));
  8. 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; } } } }
  9. 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', ));
  10. protected function getDoctrine_Orm_DefaultEntityManagerService($lazyLoad = true) { $a = new \Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain();

    $a->addDriver( new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($this->get('annotation_reader'), array($this->targetDirs[3].'/src/AppBundle/Entity')), 'AppBundle\\Entity' ); $b = new \Doctrine\ORM\Configuration(); $b->setEntityNamespaces(array('AppBundle' => 'AppBundle\\Entity')); $b->setMetadataCacheImpl($this->get('doctrine_cache.providers.doctrine.orm.default_metadata_cache')); // ... $b->setEntityListenerResolver($this->get('doctrine.orm.default_entity_listener_resolver')); $instance = \Doctrine\ORM\EntityManager::create( $this->get('doctrine.dbal.default_connection'), $b ); $this->services['doctrine.orm.default_entity_manager'] = $instance; $this->get('doctrine.orm.default_manager_configurator')->configure($instance); return $instance; }
  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. If the Container can look up services by class name,

    do we need to define ImportantService::class at all? $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. If the Container can look up services by class name,

    do we need to define LoggerFactory::class at all? $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();
  23. $container = new Illuminate\Container\Container(); $container->singleton(FileLogger::class, function ($c) { return new

    FileLogger(__DIR__.'/app.log'); }); $importantService = $container->make(ImportantService::class); $importantService->doImportantTask();
  24. If the Container can look up services by class name,

    do we need to define FileLogger::class at all? $container = new Illuminate\Container\Container(); $container->singleton(FileLogger::class, function ($c) { return new FileLogger(__DIR__.'/app.log'); }); $importantService = $container->make(ImportantService::class); $importantService->doImportantTask();
  25. class ImportantService { private $loggerFactory; public function __construct($loggerFactory) { $this->loggerFactory

    = $loggerFactory; } public function doImportantTask() { $this ->loggerFactory() ->getLogger() ->info('Did important task!'); } }
  26. 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!'); } }
  27. # 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();
  28. # 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();
  29. # 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();
  30. <!-- 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();
  31. <!-- 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();
  32. $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();
  33. $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();
  34. It had major impact on design I didn't want to

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

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

    */ } } interface Logger { public function info($message); } class FileLogger implements Logger { public function info($message) { /* ... */ } }
  37. # 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 );
  38. $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);
  39. $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);