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

Learn to Stop Wiring and Love Laravel's Container Lone Star PHP 2017

Learn to Stop Wiring and Love Laravel's Container Lone Star PHP 2017

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

April 21, 2017
Tweet

Transcript

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

    Simensen • beau.io • @beausimensen
  2. Terminology

  3. History

  4. Implementations

  5. Future

  6. None
  7. SOLID

  8. D Dependency Injection?

  9. D Dependency Inversion

  10. Inversion of Control

  11. Dependency Injection

  12. Service Locator

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

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

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

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

  18. 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']
  19. None
  20. Spring Framework's IoC Container

  21. Beans

  22. Explicit Wiring

  23. XML > Annotations

  24. <?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>
  25. Google's Guice a lightweight dependency injection framework

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

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

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

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

  31. Substrate IoC/DI Container for PHP

  32. Stones .. instead of Beans

  33. Explicit Wiring PHP Configuration

  34. $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', ));
  35. Autowiring

  36. 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; } } } }
  37. 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', ));
  38. Symfony PHP port of Spring

  39. Symfony DIC Dependency Injection Component

  40. Flexible

  41. Compiler Passes!

  42. COMPILED

  43. None
  44. Symfony & Silex

  45. Subtly Painful

  46. k.php

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

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

    = new FileLoger(__DIR__.'/app.log'); $loggerFactory = new LoggerFactory($fileLogger); $importantService = new ImportantService($loggerFactory); $importantService->doImportantTask();
  49. # 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();
  50. <!-- 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();
  51. $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();
  52. $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();
  53. $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();
  54. Using class names for services is brilliant

  55. 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; } } } }
  56. 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); } } }
  57. $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();
  58. $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();
  59. $container = new Illuminate\Container\Container(); $container->singleton(FileLogger::class, function ($c) { return new

    FileLogger(__DIR__.'/app.log'); }); $importantService = $container->make(ImportantService::class); $importantService->doImportantTask();
  60. Binding Primitives

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

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

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

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

    } }
  65. 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!'); } }
  66. # 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();
  67. # 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();
  68. # 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();
  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. <!-- 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();
  71. $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();
  72. $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();
  73. $container = new Illuminate\Container\Container(); $container ->when(FileLogger::class) ->needs('$filename') ->give(__DIR__.'/app.log'); $importantService =

    $container->make(ImportantService::class); $importantService->doImportantTask();
  74. $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!
  75. Wonderful Developer Experience

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

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

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

  79. Interface Binding

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

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

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

    $container->make(ImportantService::class); $importantService->doImportantTask();
  83. $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();
  84. $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();
  85. But what about multiple instances with different configurations? — Beau

    from like 10 years ago
  86. Contextual Binding

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

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

    */ } }
  89. # 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 );
  90. $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);
  91. $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);
  92. None
  93. Pros

  94. Amazing Developer Experience

  95. Great for heavy refactoring sessions

  96. More code writing Less configuration wrangling

  97. Magic

  98. Cons

  99. Performance

  100. Difficult to optimize

  101. Magic

  102. Best of Both Worlds?

  103. Symfony 2.8: Service Autowiring

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

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

  106. What's the point? — Entitled Beau

  107. kutny/autowiring-bundle

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

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

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

    identifier.
  111. #20264 Optional class for class named services @hason

  112. # before services: Vendor\Namespace\Class: class: Vendor\Namespace\Class autowire: true # after

    services: Vendor\Namespace\Class: autowire: true
  113. Still have to define services.

  114. dunglas/action-bundle Symfony controllers, redesigned.

  115. Scans directories Dynamically generates services with class names as the

    ID
  116. 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')); } }
  117. Despite the name of the package... It's not limited to

    actions!
  118. YAY! Performance! Optimization!

  119. Symfony Flex!

  120. ? Magic!

  121. A lot of experimentation in Dependency Injection

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

    joind.in/talk/a7abc