Slide 1

Slide 1 text

How I learned to Stop Wiring and Love Autowiring Containers joind.in/talk/666c9 Beau Simensen • beau.io • @beausimensen

Slide 2

Slide 2 text

Terminology

Slide 3

Slide 3 text

History

Slide 4

Slide 4 text

Implementations

Slide 5

Slide 5 text

Future

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

SOLID

Slide 8

Slide 8 text

D Dependency Injection?

Slide 9

Slide 9 text

D Dependency Inversion

Slide 10

Slide 10 text

Inversion of Control

Slide 11

Slide 11 text

Dependency Injection

Slide 12

Slide 12 text

Service Locator

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Wiring

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Spring Frameworks' IoC Container

Slide 21

Slide 21 text

Beans

Slide 22

Slide 22 text

Explicit Wiring

Slide 23

Slide 23 text

XML > Annotations

Slide 24

Slide 24 text

Slide 25

Slide 25 text

Google's Guice a lightweight dependency injection framework

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Slide 32

Slide 32 text

Substrate IoC/DI Container for PHP

Slide 33

Slide 33 text

Stones .. instead of Beans

Slide 34

Slide 34 text

Explicit Wiring PHP Configuration

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Autowiring

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Symfony PHP port of Spring

Slide 40

Slide 40 text

Symfony DIC Dependency Injection Component

Slide 41

Slide 41 text

Flexible

Slide 42

Slide 42 text

Compiler Passes!

Slide 43

Slide 43 text

COMPILED

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

Symfony & Silex

Slide 46

Slide 46 text

Subtly Painful

Slide 47

Slide 47 text

k.php

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

%kernel.root_dir%/app.log $importantService = $container->get('importantService'); $importantService->doImportantTask();

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

Using class names for services is brilliant

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

Binding Primitives

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

Change

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

%kernel.root_dir%/app.log $importantService = $container->get('importantService'); $importantService->doImportantTask();

Slide 71

Slide 71 text

%kernel.root_dir%/app.log $importantService = $container->get('importantService'); $importantService->doImportantTask();

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

Wonderful Developer Experience

Slide 77

Slide 77 text

XML and YAML fatigue — Beau, That Podcast episode 32

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

No more service identifiers Unless you want to use them

Slide 80

Slide 80 text

Interface Binding

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

Contextual Binding

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

No content

Slide 94

Slide 94 text

Pros

Slide 95

Slide 95 text

Amazing Developer Experience

Slide 96

Slide 96 text

Great for heavy refactoring sessions

Slide 97

Slide 97 text

More code writing, less configuration wrangling

Slide 98

Slide 98 text

Magic

Slide 99

Slide 99 text

Cons

Slide 100

Slide 100 text

Performance

Slide 101

Slide 101 text

Difficult to optimize

Slide 102

Slide 102 text

Magic

Slide 103

Slide 103 text

Best of Both Worlds?

Slide 104

Slide 104 text

Symfony 2.8: Service Autowiring

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

What's the point? — Entitled Beau

Slide 108

Slide 108 text

kutny/autowiring-bundle

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

services: service2: class: AppBundle\Service\Service2

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

#20264 Optional class for class named services @hason

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

Still have to define services.

Slide 115

Slide 115 text

dunglas/action-bundle Symfony controllers, redesigned.

Slide 116

Slide 116 text

Scans directories and dynamically generates services with class names as the ID

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

Despite the name of the package... It is not just limited to actions!

Slide 119

Slide 119 text

YAY! Performance! Optimization!

Slide 120

Slide 120 text

? Magic!

Slide 121

Slide 121 text

A lot of experimentation in Dependency Injection

Slide 122

Slide 122 text

Thanks! beau.io • @beausimensen thatpodcast.io • @thatpodcast @sensiolabs • @blackfireio joind.in/talk/666c9