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

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!

23d971deeb3975a7d28246192fbbe7b7?s=128

Beau Simensen

November 05, 2016
Tweet

Transcript

  1. Learn to Stop Wiring and Love Laravel's Container joind.in/talk/804f9 Beau

    Simensen • beau.io • @beausimensen
  2. Terminology

  3. History

  4. Laravel's Container

  5. Laravel Developer?

  6. Not a Laravel Developer?

  7. None
  8. SOLID

  9. D Dependency Injection?

  10. D Dependency Inversion

  11. Inversion of Control

  12. Dependency Injection

  13. Service Locator

  14. Dependency Inversion .-- Dependency Injection | Inversion of Control --+

    | `-- Service Locator
  15. Dependency Inversion .-- Dependency Injection --. | | Inversion of

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

    Control --+--------------------------+-- Container | | `-- Service Locator -------' Dependency Injection Container Inversion of Control Container Service Container Container
  17. 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) \ \ __/ \____\______/
  18. Containers

  19. A fancy name for an application's global state.

  20. Wiring

  21. 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']
  22. None
  23. Spring Frameworks' IoC Container

  24. Beans

  25. Explicit Wiring

  26. XML > Annotations

  27. <?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="message" class="org.springbyexample.di.app.Message"> <property name="message" value="Spring

    is fun." /> </bean> </beans>
  28. Google's Guice a lightweight dependency injection framework

  29. 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 );
  30. What if you need multiple instance with different dependencies? —

    Skeptical Beau
  31. That never happens but if it does there are ways.

    — Beau's entirely unconvincing friend
  32. Sorry R. (for the record, I should have listened to

    him...)
  33. Bring this back to PHP! where $this = IoC/DI

  34. Substrate IoC/DI Container for PHP

  35. Stones .. instead of Beans

  36. Explicit Wiring PHP Configuration

  37. $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', ));
  38. Eventually XML?

  39. Autowiring

  40. 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; } } } }
  41. 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', ));
  42. Repose + Halo + Substrate

  43. Repose My PHP port of Hibernate

  44. Doctrine PHP port of Hibernate

  45. Substrate + Halo My PHP port of Spring

  46. Symfony PHP port of Spring

  47. Symfony DIC Dependency Injection Component

  48. "services"

  49. Bah! Sorry, not everything is a "service"... — Whiny Beau

    in 2008
  50. Beans = Stones = Services (Whiny Beau got over it)

  51. Flexible

  52. Compiler Passes!

  53. COMPILED

  54. 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; }
  55. With great power comes great complexity. — Weary Beau

  56. Modern Frameworks Are Built Around an IoC/DI Container

  57. Symfony 2 Full Stack Symfony Dependency Injection Component

  58. Silex Pimple

  59. Zend Framework 2 Zend\Di

  60. Laravel Laravel Container

  61. Slim Container-Interop

  62. None
  63. Symfony & Silex

  64. Subtly Painful

  65. k.php

  66. class ImportantService { private $loggerFactory; public function __construct($loggerFactory) { $this->loggerFactory

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

    = new FileLoger(__DIR__.'/app.log'); $loggerFactory = new LoggerFactory($fileLogger); $importantService = new ImportantService($loggerFactory); $importantService->doImportantTask();
  68. # 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();
  69. <!-- 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();
  70. $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();
  71. $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();
  72. $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();
  73. Using class names for services is brilliant

  74. 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; } } } }
  75. 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); } } }
  76. 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();
  77. $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();
  78. 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();
  79. $container = new Illuminate\Container\Container(); $container->singleton(FileLogger::class, function ($c) { return new

    FileLogger(__DIR__.'/app.log'); }); $importantService = $container->make(ImportantService::class); $importantService->doImportantTask();
  80. 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();
  81. Binding Primitives

  82. $container = new Illuminate\Container\Container(); $container ->when(FileLogger::class) ->needs('$filename') ->give(__DIR__.'/app.log'); $importantService =

    $container->make(ImportantService::class); $importantService->doImportantTask();
  83. Change

  84. class ImportantService { private $loggerFactory; public function __construct($loggerFactory) { $this->loggerFactory

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

    } }
  86. 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!'); } }
  87. # 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();
  88. # 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();
  89. # 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();
  90. <!-- 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();
  91. <!-- 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();
  92. $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();
  93. $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();
  94. $container = new Illuminate\Container\Container(); $container ->when(FileLogger::class) ->needs('$filename') ->give(__DIR__.'/app.log'); $importantService =

    $container->make(ImportantService::class); $importantService->doImportantTask();
  95. $container = new Illuminate\Container\Container(); $container ->when(FileLogger::class) ->needs('$filename') ->give(__DIR__.'/app.log'); $importantService =

    $container->make(ImportantService::class); $importantService->doImportantTask(); Nothing But Win!
  96. Wonderful Developer Experience

  97. XML and YAML fatigue — Beau, That Podcast episode 32

  98. It had major impact on design I didn't want to

    have to tackle configuration
  99. No more service identifiers Unless you want to use them

  100. Less fiddling with config while refactoring Though sometimes you might

    need to...
  101. Interface Binding

  102. class LoggerFactory { public function __construct(FileLogger $fileLogger) { /* ...

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

    */ } } interface Logger { public function info($message); } class FileLogger implements Logger { public function info($message) { /* ... */ } }
  104. $container = new Illuminate\Container\Container(); $container ->when(FileLogger::class) ->needs('$filename') ->give(__DIR__.'/app.log'); $importantService =

    $container->make(ImportantService::class); $importantService->doImportantTask();
  105. $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); $importantService->doImportantTask();
  106. Swapping out implementations?

  107. $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); $importantService->doImportantTask();
  108. $container = new Illuminate\Container\Container(); $container ->when(FileLogger::class) ->needs('$filename') ->give(__DIR__.'/app.log'); $container->bind(Logger::class, SylogLogger::class);

    $importantService = $container->make(ImportantService::class); $importantService->doImportantTask();
  109. But what about multiple instances with different configurations? — Beau

    from like 10 years ago
  110. Contextual Binding

  111. class NullLogger implements Logger { public function info($message) { //

    noop } }
  112. class UnimportantService { public function __construct(LoggerFactory $loggerFactory) { /* ..

    */ } }
  113. # 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 );
  114. $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);
  115. $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);
  116. None
  117. Pros

  118. Amazing Developer Experience

  119. Great for heavy refactoring sessions

  120. More code writing, less configuration wrangling

  121. Magic

  122. Cons

  123. Performance

  124. Difficult to optimize

  125. Magic

  126. Best of Both Worlds?

  127. Symfony 2.8: Service Autowiring

  128. # app/config/services.yml services: service1: class: AppBundle\Service\Service1 service2: class: AppBundle\Service\Service2 arguments:

    ['@service1']
  129. # app/config/services.yml services: service2: class: AppBundle\Service\Service2 autowire: true

  130. Still have to define services and manually mark them as

    autowired.
  131. What's the point? — Entitled Beau

  132. kutny/autowiring-bundle

  133. services: service2: class: AppBundle\Service\Service2 autowire: true

  134. services: service2: class: AppBundle\Service\Service2

  135. Still have to define services and assign them a service

    identifier.
  136. Maybe someday...

  137. Laravel's Container FTW

  138. Autowiring Containers FTW

  139. Other containers that support autowiring?

  140. Thanks! beau.io • @beausimensen thatpodcast.io • @thatpodcast @sensiolabs • @blackfireio

    joind.in/talk/804f9