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

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!

Beau Simensen

April 21, 2017
Tweet

More Decks by Beau Simensen

Other Decks in Programming

Transcript

  1. Learn to Stop Wiring
    and Love Laravel's
    Container
    joind.in/talk/a7abc
    Beau Simensen • beau.io • @beausimensen

    View Slide

  2. Terminology

    View Slide

  3. History

    View Slide

  4. Implementations

    View Slide

  5. Future

    View Slide

  6. View Slide

  7. SOLID

    View Slide

  8. D
    Dependency Injection?

    View Slide

  9. D
    Dependency Inversion

    View Slide

  10. Inversion of
    Control

    View Slide

  11. Dependency
    Injection

    View Slide

  12. Service Locator

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  17. Wiring

    View Slide

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

    View Slide

  19. View Slide

  20. Spring Framework's
    IoC Container

    View Slide

  21. Beans

    View Slide

  22. Explicit Wiring

    View Slide

  23. XML > Annotations

    View Slide



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



    View Slide

  25. Google's Guice
    a lightweight dependency
    injection framework

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide


  31. View Slide

  32. Substrate
    IoC/DI Container for PHP

    View Slide

  33. Stones
    .. instead of Beans

    View Slide

  34. Explicit Wiring
    PHP Configuration

    View Slide

  35. $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

  36. Autowiring

    View Slide

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

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

    View Slide

  39. Symfony
    PHP port of Spring

    View Slide

  40. Symfony DIC
    Dependency Injection Component

    View Slide

  41. Flexible

    View Slide

  42. Compiler Passes!

    View Slide

  43. COMPILED

    View Slide

  44. View Slide

  45. Symfony & Silex

    View Slide

  46. Subtly Painful

    View Slide

  47. k.php

    View Slide

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

    View Slide

  49. # k.php
    class ImportantService { /* ... */ }
    $fileLogger = new FileLoger(__DIR__.'/app.log');
    $loggerFactory = new LoggerFactory($fileLogger);
    $importantService = new ImportantService($loggerFactory);
    $importantService->doImportantTask();

    View Slide

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

    View Slide






  51. %kernel.root_dir%/app.log









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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  55. Using class names for
    services is brilliant

    View Slide

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

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

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

    View Slide

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

    View Slide

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

    View Slide

  61. Binding Primitives

    View Slide

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

    View Slide

  63. Change

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide






  70. %kernel.root_dir%/app.log









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

    View Slide






  71. %kernel.root_dir%/app.log











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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  76. Wonderful Developer Experience

    View Slide

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

    View Slide

  78. It had major impact on
    design
    I didn't want to have to tackle
    configuration

    View Slide

  79. No more service
    identifiers
    Unless you want to use them

    View Slide

  80. Interface
    Binding

    View Slide

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

    View Slide

  82. class LoggerFactory {
    public function __construct(Logger $logger) { /* ... */ }
    }
    interface Logger {
    public function info($message);
    }
    class FileLogger implements Logger {
    public function info($message) { /* ... */ }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  87. Contextual Binding

    View Slide

  88. class NullLogger implements Logger {
    public function info($message) {
    // noop
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  93. View Slide

  94. Pros

    View Slide

  95. Amazing Developer Experience

    View Slide

  96. Great for heavy refactoring sessions

    View Slide

  97. More code writing
    Less configuration wrangling

    View Slide

  98. Magic

    View Slide

  99. Cons

    View Slide

  100. Performance

    View Slide

  101. Difficult to optimize

    View Slide

  102. Magic

    View Slide

  103. Best of Both
    Worlds?

    View Slide

  104. Symfony 2.8: Service
    Autowiring

    View Slide

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

    View Slide

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

    View Slide

  107. What's the point?
    — Entitled Beau

    View Slide

  108. kutny/autowiring-bundle

    View Slide

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

    View Slide

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

    View Slide

  111. Still have to define
    services and assign them
    a service identifier.

    View Slide

  112. #20264
    Optional class for class named services
    @hason

    View Slide

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

    View Slide

  114. Still have to define
    services.

    View Slide

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

    View Slide

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

    View Slide

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

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

    View Slide

  119. YAY!
    Performance!
    Optimization!

    View Slide

  120. Symfony Flex!

    View Slide

  121. ?
    Magic!

    View Slide

  122. A lot of experimentation in
    Dependency
    Injection

    View Slide

  123. Thanks!
    beau.io • @beausimensen
    thatpodcast.io • @thatpodcast
    @sensiolabs • @blackfireio
    joind.in/talk/a7abc

    View Slide