$30 off During Our Annual Pro Sale. View Details »

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.

Beau Simensen

January 16, 2020
Tweet

More Decks by Beau Simensen

Other Decks in Programming

Transcript

  1. How I learned to Stop Wiring and
    Love Autowiring Containers
    Beau Simensen • beausimensen.com • @beausimensen

    View Slide

  2. Terminology

    View Slide

  3. History

    View Slide

  4. Implementations

    View Slide

  5. View Slide

  6. SOLID

    View Slide

  7. D
    Dependency Injection?

    View Slide

  8. D
    Dependency Inversion

    View Slide

  9. Inversion of Control

    View Slide

  10. Dependency Injection

    View Slide

  11. Service Locator

    View Slide

  12. Dependency Inversion .-- Dependency Injection
    |
    Inversion of Control --+
    |
    `-- Service Locator

    View Slide

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

    View Slide

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

    View Slide

  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) \ \ __/
    \____\______/

    View Slide

  16. Wiring

    View Slide

  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']

    View Slide

  18. View Slide

  19. Spring Framework's
    IoC Container

    View Slide

  20. Beans

    View Slide

  21. Explicit Wiring

    View Slide

  22. XML > Annotations

    View Slide



  23. class="org.springbyexample.di.app.Message">



    View Slide

  24. Google's Guice
    a lightweight dependency
    injection framework

    View Slide

  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
    );

    View Slide

  26. What if you need multiple
    instance with different
    dependencies?
    — Skeptical Beau

    View Slide

  27. That never happens but if
    it does there are ways.
    — Beau's entirely unconvincing friend

    View Slide

  28. Sorry R.
    (for the record, I should have listened to him...)

    View Slide

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

    View Slide


  30. View Slide

  31. Substrate
    IoC/DI Container for PHP

    View Slide

  32. Stones
    .. instead of Beans

    View Slide

  33. Explicit Wiring
    PHP Configuration

    View Slide

  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',
    ));

    View Slide

  35. Autowiring

    View Slide

  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;
    }
    }
    }
    }

    View Slide

  37. 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',
    ));

    View Slide

  38. Symfony
    PHP port of Spring

    View Slide

  39. Symfony DIC
    Dependency Injection Component

    View Slide

  40. Flexible

    View Slide

  41. Compiler Passes!

    View Slide

  42. COMPILED

    View Slide

  43. View Slide

  44. k.php

    View Slide

  45. class ImportantService {
    private $logger;
    public function __construct($logger) {
    $this->logger = $logger;
    }
    public function doImportantTask() {
    $this
    ->logger
    ->info('Did important task!');
    }
    }

    View Slide

  46. # k.php
    class ImportantService { /* ... */ }
    $fileLogDriver = new FileLogDriver(__DIR__.'/app.log');
    $logger = new Logger($fileLogDriver);
    $importantService = new ImportantService($logger);
    $importantService->doImportantTask();

    View Slide

  47. # 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();

    View Slide






  48. %kernel.root_dir%/app.log









    $importantService = $container->get('importantService');
    $importantService->doImportantTask();

    View Slide

  49. $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();

    View Slide

  50. $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();

    View Slide

  51. $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();

    View Slide

  52. Using class names for services is brilliant

    View Slide

  53. 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;
    }
    }
    }
    }

    View Slide

  54. 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);
    }
    }
    }

    View Slide

  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));
    });
    $container->singleton(ImportantService::class, function ($c) {
    return new ImportantService($c->make(Logger::class));
    });
    $importantService = $container->make(ImportantService::class);
    $importantService->doImportantTask();

    View Slide

  56. $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();

    View Slide

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

    View Slide

  58. Binding Primitives

    View Slide

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

    View Slide

  60. Change

    View Slide

  61. class ImportantService {
    private $logger;
    public function __construct($logger) {
    $this->logger = $logger;
    }
    public function doImportantTask() {
    $this
    ->logger
    ->info('Did important task!');
    }
    }

    View Slide

  62. class Connection {
    public function execute() { /* ... */ }
    }

    View Slide

  63. 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!');
    }
    }

    View Slide

  64. # 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();

    View Slide

  65. # 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();

    View Slide

  66. # 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();

    View Slide






  67. %kernel.root_dir%/app.log









    $importantService = $container->get('importantService');
    $importantService->doImportantTask();

    View Slide






  68. %kernel.root_dir%/app.log











    $importantService = $container->get('importantService');
    $importantService->doImportantTask();

    View Slide

  69. $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();

    View Slide

  70. $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();

    View Slide

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

    View Slide

  72. $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!

    View Slide

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

    View Slide

  74. Pros

    View Slide

  75. Amazing Developer Experience

    View Slide

  76. Great for heavy refactoring sessions

    View Slide

  77. More code writing
    Less configuration wrangling

    View Slide

  78. Magic

    View Slide

  79. Cons

    View Slide

  80. Performance

    View Slide

  81. Difficult to optimize

    View Slide

  82. Magic

    View Slide

  83. View Slide

  84. Best of Both Worlds?

    View Slide

  85. Symfony
    +
    Autowiring

    View Slide

  86. Service Autowiring
    Symfony 2.8

    View Slide

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

    View Slide

  88. # app/config/services.yml
    services:
    service2:
    class: AppBundle\Service\Service2
    autowire: true

    View Slide

  89. What's the point?
    — Entitled Beau

    View Slide

  90. kutny/autowiring-bundle

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  94. Scans directories
    Dynamically generates services
    with class names as the ID

    View Slide

  95. 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'));
    }
    }

    View Slide

  96. Despite the name of the package...
    It's not limited to actions!

    View Slide

  97. YAY!
    Performance! Optimization!
    Developer Experience!

    View Slide

  98. Symfony 3.3

    View Slide

  99. #20264 – Symfony 3.3
    Optional class for class named services
    @hason

    View Slide

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

    View Slide

  101. #21071 – Symfony 3.3
    Configurable defaults for public, tags and autowiring
    @nicolas-grekas

    View Slide

  102. # before
    services:
    Foo:
    public: false
    autowire: true
    Bar:
    public: false
    autowire: true
    # after
    services:
    _defaults:
    public: false
    autowire: true
    Foo: ~
    Bar: ~

    View Slide

  103. #21289 – Symfony 3.3
    PSR4-based discovery and registration
    @nicolas-grekas

    View Slide

  104. services:
    App\:
    # relative to the current file
    resources: ../src/{Controller,Command}
    # or any other attributes
    autowire: true

    View Slide

  105. #21530 – Symfony 3.3
    _instanceof: local interface-defined configs
    @nicolas-grekas

    View Slide

  106. 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}

    View Slide

  107. #22234 – Symfony 3.3
    automatic _instanceof configuration
    @weaverryan

    View Slide

  108. # 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: ~

    View Slide

  109. #21383 – Symfony 3.3
    Named arguments
    @dunglas

    View Slide

  110. 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;
    }
    }

    View Slide

  111. # 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

    View Slide

  112. #21771 – Symfony 3.3
    "controller.service_arguments" tag
    inject services into actions
    @nicolas-grekas

    View Slide

  113. 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'));
    }
    }

    View Slide

  114. Symfony Flex!

    View Slide

  115. 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']

    View Slide

  116. Real Life

    View Slide

  117. 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

    View Slide

  118. 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(),
    ]);
    }
    }

    View Slide

  119. 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));
    }
    }

    View Slide

  120. src/Doctrine/
    └── SchemaNamespaceFixListener.php

    View Slide

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

    View Slide

  122. 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

    View Slide

  123. src/Validator/
    ├── UniqueBusinessUsername.php
    ├── UniqueBusinessUsernameValidator.php
    ├── UniqueUserEmailAddress.php
    ├── UniqueUserEmailAddressValidator.php
    ├── UniqueUserUsername.php
    └── UniqueUserUsernameValidator.php

    View Slide

  124. 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();
    }
    }

    View Slide

  125. 49 classes

    View Slide

  126. 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']

    View Slide

  127. # ...
    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 }

    View Slide

  128. View Slide

  129. View Slide

  130. Interface Binding

    View Slide

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

    View Slide

  132. class Logger {
    public function __construct(Logger $logger) { /* ... */ }
    }
    interface LogDriver {
    public function info($message);
    }
    class FileLogDriver implements LogDriver {
    public function info($message) { /* ... */ }
    }

    View Slide

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

    View Slide

  134. $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();

    View Slide

  135. $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();

    View Slide

  136. But what about multiple
    instances with different
    configurations?
    — Beau from like 10 years ago

    View Slide

  137. Contextual Binding

    View Slide

  138. class NullLogDriver implements LogDriver {
    public function info($message) {
    // noop
    }
    }

    View Slide

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

    View Slide

  140. # 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
    );

    View Slide

  141. $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);

    View Slide

  142. $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);

    View Slide

  143. View Slide

  144. Still not convinced?

    View Slide

  145. Configuration ends up being
    exceptions

    View Slide

  146. You can always wire manually if
    something breaks

    View Slide

  147. Give it a try. :)

    View Slide

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

    View Slide