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

How I learned to Stop Wiring and Love Autowiring Containers

How I learned to Stop Wiring and Love Autowiring Containers

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.

23d971deeb3975a7d28246192fbbe7b7?s=128

Beau Simensen

January 16, 2020
Tweet

Transcript

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

    Beau Simensen • beausimensen.com • @beausimensen
  2. Terminology

  3. History

  4. Implementations

  5. None
  6. SOLID

  7. D Dependency Injection?

  8. D Dependency Inversion

  9. Inversion of Control

  10. Dependency Injection

  11. Service Locator

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

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

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

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

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

  20. Beans

  21. Explicit Wiring

  22. XML > Annotations

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

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

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

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

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

  30. Substrate IoC/DI Container for PHP

  31. Stones .. instead of Beans

  32. Explicit Wiring PHP Configuration

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

  35. 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; } } } }
  36. class dd_logging_FileLogDriver { public function __construct($filename) { /* .. */

    } } class dd_logging_LogFactory { public function __construct(dd_logging_FileLogDriver $logger) { /* .. */ } } $context->add('logger', array( 'className' => 'dd_logging_FileLogDriver', 'constructorArgs' => array( 'filename' => __DIR__.'/app.log', ), )); $context->add('logFactory', array( 'className' => 'dd_logging_LogFactory', ));
  37. Symfony PHP port of Spring

  38. Symfony DIC Dependency Injection Component

  39. Flexible

  40. Compiler Passes!

  41. COMPILED

  42. None
  43. k.php

  44. class ImportantService { private $logger; public function __construct($logger) { $this->logger

    = $logger; } public function doImportantTask() { $this ->logger ->info('Did important task!'); } }
  45. # k.php class ImportantService { /* ... */ } $fileLogDriver

    = new FileLogDriver(__DIR__.'/app.log'); $logger = new Logger($fileLogDriver); $importantService = new ImportantService($logger); $importantService->doImportantTask();
  46. # services.yml services: fileLogDriver: class: 'FileLogDriver' arguments: ['%kernel.root_dir%/app.log'] logger: class:

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

    id="fileLogDriver" class="FileLogDriver"> <argument>%kernel.root_dir%/app.log</argument> </service> <service id="logger" class="Logger"> <argument type="service" id="fileLogDriver" /> </service> <service id="importantService" class="ImportantService"> <argument type="service" id="logger" /> </service> </services> </container> $importantService = $container->get('importantService'); $importantService->doImportantTask();
  48. $container = new Pimple\Container(); $container['fileLogDriver'] = function () { return

    new FileLogDriver(__DIR__.'/app.log'); }; $container['logger'] = function ($c) { return new Logger($c['fileLogDriver']); }; $container['importantService'] = function ($c) { return new ImportantService($c['logger']); }; $importantService = $container['importantService']; $importantService->doImportantTask();
  49. $container = new Illuminate\Container\Container(); $container->singleton('fileLogDriver', function ($c) { return new

    FileLogDriver(__DIR__.'/app.log'); }); $container->singleton('logger', function ($c) { return new Logger($c->make('fileLogDriver')); }); $container->singleton('importantService', function ($c) { return new ImportantService($c->make('logger')); }); $importantService = $container->make('importantService'); $importantService->doImportantTask();
  50. $container = new Illuminate\Container\Container(); $container->singleton(FileLogDriver::class, function ($c) { return new

    FileLogDriver(__DIR__.'/app.log'); }); $container->singleton(Logger::class, function ($c) { return new Logger($c->make(FileLogDriver::class)); }); $container->singleton(ImportantService::class, function ($c) { return new ImportantService($c->make(Logger::class)); }); $importantService = $container->make(ImportantService::class); $importantService->doImportantTask();
  51. Using class names for services is brilliant

  52. 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; } } } }
  53. 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); } } }
  54. $container = new Illuminate\Container\Container(); $container->singleton(FileLogDriver::class, function ($c) { return new

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

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

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

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

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

  60. class ImportantService { private $logger; public function __construct($logger) { $this->logger

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

    } }
  62. class ImportantService { private $logger; private $connection; public function __construct($logger,

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

    ImportantService { /* ... */ } $fileLogDriver = new FileLogDriver(__DIR__.'/app.log'); $logger = new Logger($fileLogDriver); $connection = new Connection(); $importantService = new ImportantService( $logger, $connection ); $importantService->doImportantTask();
  64. # services.yml services: fileLogDriver: class: 'FileLogDriver' arguments: ['%kernel.root_dir%/app.log'] logger: class:

    'Logger' arguments: ['@fileLogDriver'] importantService: class: 'ImportantService' arguments: ['@logger'] $importantService = $container->get('importantService'); $importantService->doImportantTask();
  65. # services.yml services: fileLogDriver: class: 'FileLogDriver' arguments: ['%kernel.root_dir%/app.log'] logger: class:

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

    id="fileLogDriver" class="FileLogDriver"> <argument>%kernel.root_dir%/app.log</argument> </service> <service id="logger" class="Logger"> <argument type="service" id="fileLogDriver" /> </service> <service id="importantService" class="ImportantService"> <argument type="service" id="logger" /> </service> </services> </container> $importantService = $container->get('importantService'); $importantService->doImportantTask();
  67. <!-- app/config/services.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container> <services> <service

    id="fileLogDriver" class="FileLogDriver"> <argument>%kernel.root_dir%/app.log</argument> </service> <service id="logger" class="Logger"> <argument type="service" id="fileLogDriver" /> </service> <service id="connection" class="Connection" /> <service id="importantService" class="ImportantService"> <argument type="service" id="logger" /> <argument type="service" id="connection" /> </service> </services> </container> $importantService = $container->get('importantService'); $importantService->doImportantTask();
  68. $container = new Pimple\Container(); $container['fileLogDriver'] = function () { return

    new FileLogDriver(__DIR__.'/app.log'); }; $container['logger'] = function ($c) { return new Logger($c['fileLogDriver']); }; $container['importantService'] = function ($c) { return new ImportantService($c['logger']); }; $importantService = $container['importantService']; $importantService->doImportantTask();
  69. $container = new Pimple\Container(); $container['fileLogDriver'] = function () { return

    new FileLogDriver(__DIR__.'/app.log'); }; $container['logger'] = function ($c) { return new Logger($c['fileLogDriver']); }; $container['connection'] = function ($c) { return new Connection(); }; $container['importantService'] = function ($c) { return new ImportantService( $c['logger'], $c['connection'] ); }; $importantService = $container['importantService']; $importantService->doImportantTask();
  70. $container = new Illuminate\Container\Container(); $container ->when(FileLogDriver::class) ->needs('$filename') ->give(__DIR__.'/app.log'); $importantService =

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

    $container->make(ImportantService::class); $importantService->doImportantTask(); Nothing But Win!
  72. XML and YAML fatigue — Beau, That Podcast episode 32

  73. Pros

  74. Amazing Developer Experience

  75. Great for heavy refactoring sessions

  76. More code writing Less configuration wrangling

  77. Magic

  78. Cons

  79. Performance

  80. Difficult to optimize

  81. Magic

  82. None
  83. Best of Both Worlds?

  84. Symfony + Autowiring

  85. Service Autowiring Symfony 2.8

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

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

  88. What's the point? — Entitled Beau

  89. kutny/autowiring-bundle

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

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

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

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

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

    actions!
  96. YAY! Performance! Optimization! Developer Experience!

  97. Symfony 3.3

  98. #20264 – Symfony 3.3 Optional class for class named services

    @hason
  99. # before services: Vendor\Namespace\Class: class: Vendor\Namespace\Class autowire: true # after

    services: Vendor\Namespace\Class: autowire: true
  100. #21071 – Symfony 3.3 Configurable defaults for public, tags and

    autowiring @nicolas-grekas
  101. # before services: Foo: public: false autowire: true Bar: public:

    false autowire: true # after services: _defaults: public: false autowire: true Foo: ~ Bar: ~
  102. #21289 – Symfony 3.3 PSR4-based discovery and registration @nicolas-grekas

  103. services: App\: # relative to the current file resources: ../src/{Controller,Command}

    # or any other attributes autowire: true
  104. #21530 – Symfony 3.3 _instanceof: local interface-defined configs @nicolas-grekas

  105. services: _defaults: public: false _instanceof: Symfony\Component\Console\Command\Command: tags: ['console.command'] public: true

    # commands must be public Twig_ExtensionInterface: tags: ['twig.extension'] Symfony\Component\EventDispatcher\EventSubscriberInterface: tags: ['kernel.event_subscriber'] App\: resource: ../src/{Action,Command,EventSubscriber,Twig}
  106. #22234 – Symfony 3.3 automatic _instanceof configuration @weaverryan

  107. # before services: _defaults: autowire: true _instanceof: Symfony\Component\EventDispatcher\EventSubscriberInterface: tags: [kernel.event_subscriber]

    # service using the above tag AppBundle\EventListener\CheckRequirementsSubscriber: ~ # after services: _defaults: autowire: true autoconfigure: true # service using the auto_configure_instanceof functionality AppBundle\EventListener\CheckRequirementsSubscriber: ~
  108. #21383 – Symfony 3.3 Named arguments @dunglas

  109. class NewsletterManager { private $logger; private $em; private $apiKey; public

    function __construct( LoggerInterface $logger, EntityManager $em, $apiKey ) { $this->logger = $logger; $this->em = $em; $this->apiKey = $apiKey; } }
  110. # New awesome syntax services: _defaults: { autowire: true }

    Acme\NewsletterManager: { $apiKey: "%mandrill_api_key%" } # Alternative (more traditional) syntax services: newsletter_manager: class: Acme\NewsletterManager arguments: $apiKey: "%mandrill_api_key%" autowire: true
  111. #21771 – Symfony 3.3 "controller.service_arguments" tag inject services into actions

    @nicolas-grekas
  112. class Home { /** * @Route("/myaction", name="my_action") */ public function

    myAction(Request $request, Router $router, Twig $twig) { if (!$request->isMethod('GET')) { return new RedirectResponse($router->generateUrl('my_action'), 301); } return new Response($twig->render('mytemplate.html.twig')); } }
  113. Symfony Flex!

  114. services: _defaults: autowire: true autoconfigure: true public: false App\: resource:

    '../../src/{Command,Form,EventSubscriber,Twig,Voter}' App\Controller\: resource: '../../src/Controller' public: true tags: ['controller.service_arguments']
  115. Real Life

  116. src/Controller/ ├── AccountBusinessFirstTimeSetupController.php ├── Businesses │ ├── Audiences │ │

    └── Connect │ │ └── ConnectTwitterAudienceController.php │ ├── AudiencesController.php │ ├── Stores │ │ └── Connect │ │ └── ConnectEtsyStoreController.php │ └── StoresController.php ├── DashboardController.php ├── PricingController.php ├── SecurityController.php └── UserController.php
  117. class SecurityController extends Controller { /** * @Route("/login", name="login", methods={"GET"})

    */ public function login(AuthenticationUtils $authUtils) { return $this->render('security/login.html.twig', [ 'last_username' => $authUtils->getLastUsername(), 'error' => $authUtils->getLastAuthenticationError(), ]); } }
  118. class ConnectEtsyStoreController extends Controller { private $serverFactory; private $oauthRepository; public

    function __construct( EtsyOauthServerFactory $serverFactory, EtsyOauthRepository $oauthRepository, ) { $this->serverFactory = $serverFactory; $this->oauthRepository = $oauthRepository; } /** * @Route("/account/businesses/{business}/stores/connect/etsy", name="etsy_store_connect") */ public function connect(Request $request, SessionInterface $session, Business $business) { $server = $this->serverFactory->create($business); $temporaryCredentials = $server->getTemporaryCredentials(); $session->set('etsyoauth_token', $temporaryCredentials->getIdentifier()); $session->set('etsyoauth_token_secret', $temporaryCredentials->getSecret()); return $this->redirect($server->getAuthorizationUrl($temporaryCredentials)); } }
  119. src/Doctrine/ └── SchemaNamespaceFixListener.php

  120. src/EventSubscriber/ ├── BusinessSubscriptionOnboardingProcessSubscriber.php └── BusinessSubscriptionOnboardingSubscriber.php

  121. src/Integration/Store/Etsy/ ├── EtsyApiClient │ ├── EtsyApiClient.php │ ├── Requests │

    │ ├── Associations.php │ │ ├── Listing │ │ │ ├── FindAllShopListingsActive.php │ │ │ └── FindAllShopSectionListingsActive.php │ │ ├── Pagination.php │ │ ├── RenderedRequest.php │ │ ├── Request.php │ │ └── Shop │ │ └── FindAllShopSections.php │ └── Response.php └── Oauth ├── EtsyOauthServer.php └── EtsyOauthServerFactory.php
  122. src/Validator/ ├── UniqueBusinessUsername.php ├── UniqueBusinessUsernameValidator.php ├── UniqueUserEmailAddress.php ├── UniqueUserEmailAddressValidator.php ├──

    UniqueUserUsername.php └── UniqueUserUsernameValidator.php
  123. class UniqueBusinessUsernameValidator extends ConstraintValidator { /** * @var BusinessRepository */

    private $businessRepository; public function __construct(BusinessRepository $businessRepository) { $this->businessRepository = $businessRepository; } public function validate($value, Constraint $constraint) { $business = $this->businessRepository->findByUsername($value); if (is_null($business)) { return; } $this->context->buildViolation($constraint->message) ->setParameter('{{ value }}', $value) ->addViolation(); } }
  124. 49 classes

  125. services: _defaults: autowire: true autoconfigure: true App\: resource: '../src/*' exclude:

    '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}' App\Controller\: resource: '../src/Controller' tags: ['controller.service_arguments']
  126. # ... App\Integration\Store\Etsy\EtsyApiClient\EtsyApiClient: arguments: $consumerKey: '%env(ETSYOAUTH_CONSUMER_KEY)%' $consumerSecret: '%env(ETSYOAUTH_CONSUMER_SECRET)%' App\Integration\Store\Etsy\Oauth\EtsyOauthServerFactory: arguments:

    $consumerKey: '%env(ETSYOAUTH_CONSUMER_KEY)%' $consumerSecret: '%env(ETSYOAUTH_CONSUMER_SECRET)%' App\Controller\Businesses\Audiences\Connect\ConnectTwitterAudienceController: arguments: $consumerKey: '%env(TWITTEROAUTH_CONSUMER_KEY)%' $consumerSecret: '%env(TWITTEROAUTH_CONSUMER_SECRET)%' App\Doctrine\SchemaNamespaceFixListener: tags: - { name: doctrine.event_subscriber, connection: default }
  127. None
  128. None
  129. Interface Binding

  130. class Logger { public function __construct(FileLogDriver $fileLogDriver) { /* ...

    */ } } class FileLogDriver { public function info($message) { /* ... */ } }
  131. class Logger { public function __construct(Logger $logger) { /* ...

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

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

    $importantService = $container->make(ImportantService::class); $importantService->doImportantTask();
  134. $container = new Illuminate\Container\Container(); $container ->when(FileLogDriver::class) ->needs('$filename') ->give(__DIR__.'/app.log'); $container->bind(LogDriver::class, SylogLogger::class);

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

    from like 10 years ago
  136. Contextual Binding

  137. class NullLogDriver implements LogDriver { public function info($message) { //

    noop } }
  138. class UnimportantService { public function __construct(Logger $logger) { /* ..

    */ } }
  139. # k.php class Connection { /* ... */ } class

    ImportantService { /* ... */ } class UnimportantService { /* ... */ } $fileLogDriver = new FileLogDriver(__DIR__.'/app.log'); $fileLogDriver = new Logger($fileLogDriver); $connection = new Connection(); $importantService = new ImportantService( $fileLogDriver, $connection ); $nullLogDriver = new NullLogDriver(); $nullLogger = new Logger($nullLogDriver); $unimportantService = new UnimportantService( $nullLogger );
  140. $container = new Illuminate\Container\Container(); $container ->when(FileLogDriver::class) ->needs('$filename') ->give(__DIR__.'/app.log'); $container->bind(LogDriver::class, FileLogDriver::class);

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

    $container ->when(UnimportantService::class) ->needs(LogDriver::class) ->give(function ($c) { $nullLogDriver = $c->make(NullLogDriver::class); return new Logger($nullLogDriver); }); $importantService = $container->make(ImportantService::class); $unimportantService = $container->make(UnimportantService::class);
  142. None
  143. Still not convinced?

  144. Configuration ends up being exceptions

  145. You can always wire manually if something breaks

  146. Give it a try. :)

  147. Thanks! beausimensen.com • @beausimensen thatpodcast.io • @thatpodcast astrocasts.com • @astrocasts