Slide 1

Slide 1 text

Taming Dependency Injection @nicolasgrekas

Slide 2

Slide 2 text

@nicolasgrekas services: _defaults: autowire: true # Look at parameter types to auto-inject deps autoconfigure: true # Look at parent types to auto-register classes # Load every class in a namespace/directory as a service App\: resource: '../src/' # Add more service definitions when explicit configuration is needed config/services.yaml

Slide 3

Slide 3 text

Auto-discovery Auto-wiring Auto-configuration Three key mechanisms @nicolasgrekas

Slide 4

Slide 4 text

Auto-discovery Scan src/ for classes Register them as %FQCN% services Account for: #[When] #[WhenNot] #[Exclude] #[AsAlias] / singly-implemented interfaces @nicolasgrekas

Slide 5

Slide 5 text

Auto-wiring Figure out missing dependencies By FQCN name, not "instanceof" checks By #[Autowire] attribute @nicolasgrekas

Slide 6

Slide 6 text

@nicolasgrekas

Slide 7

Slide 7 text

@nicolasgrekas

Slide 8

Slide 8 text

Auto-configuration If it looks like a Command, make it a command @nicolasgrekas

Slide 9

Slide 9 text

@nicolasgrekas FrameworkBundle's FrameworkExtension $container->registerForAutoconfiguration(Command::class) $container->registerForAutoconfiguration(AbstractController::class) $container->registerForAutoconfiguration(FormTypeInterface::class) $container->registerForAutoconfiguration(EventSubscriberInterface::class) $container->registerForAutoconfiguration(ResetInterface::class) $container->registerForAutoconfiguration(MessageHandlerInterface::class) // ...

Slide 10

Slide 10 text

@nicolasgrekas FrameworkBundle's FrameworkExtension $container->registerAttributeForAutoconfiguration(AsEventListener::class, ...) $container->registerAttributeForAutoconfiguration(AsController::class, ...) $container->registerAttributeForAutoconfiguration(AsRemoteEventConsumer::class, ...) $container->registerAttributeForAutoconfiguration(AsMessageHandler::class, ...) $container->registerAttributeForAutoconfiguration(AsSchedule::class, ...) // ...

Slide 11

Slide 11 text

@nicolasgrekas Just Enjoy on your side #[AsEventListener] class MyRequestListener { public function __invoke(RequestEvent $event): void { // ... } }

Slide 12

Slide 12 text

@nicolasgrekas #[AutoconfigureTag] interface ChannelInterface { } Define your rules

Slide 13

Slide 13 text

@nicolasgrekas Enjoy your rules class ChannelBroadcaster { public function __construct( #[AutowireIterator(ChannelInterface::class)] iterable $channels ) { // ... }

Slide 14

Slide 14 text

@nicolasgrekas #[AsAlias(ChannelInterface::class)] class DefaultChannel implements ChannelInterface {

Slide 15

Slide 15 text

Dependency Injection #[AsDecorator] #[AsTaggedItem] #[AutowireCallable] #[AutowireDecorated] #[AutowireLocator] #[AutowireServiceClosure] #[Required] #[SubscribedService] #[Target] @nicolasgrekas

Slide 16

Slide 16 text

@nicolasgrekas #[Autoconfigure(lazy: true)] class PlotFactory { public function __construct( #[Autowire(param: 'kernel.debug')] private bool $debug, private HeavyDependency $heavyDependency, ) { } }

Slide 17

Slide 17 text

public function __construct( #[AutowireLocator([ 'router' => RouterInterface::class, 'slugger' => '?'.SluggerInterface::class, ])] ContainerInterface $container, ) @nicolasgrekas

Slide 18

Slide 18 text

@nicolasgrekas public function __construct( #[AutowireCallable(UriTemplate::class, 'expand')] UriExpanderInterface $expander, ) new class($uriTemplate->expand(...)) implements UriExpanderInterface { public function __construct( private \Closure $closure, ) { } public function expand(string $url, array $vars): string { return $this->closure->__invoke($url, $vars); } } $uriTemplate->expand(...)

Slide 19

Slide 19 text

$container->registerAttributeForAutoconfiguration(JsonStreamable::class, […] $definition->addResourceTag('json_streamer.streamable', [ 'object' => $attribute->asObject, 'list' => $attribute->asList, ]); }); @nicolasgrekas

Slide 20

Slide 20 text

class StreamablePass implements CompilerPassInterface { public function process(ContainerBuilder $container): void { $streamableClasses = []; $taggedServices = $container->findTaggedResourceIds('json_streamer.streamable'); foreach ($taggedServices as $id => $attributes) { $className = $container->getDefinition($id)->getClass(); $streamableClasses[$className] = [ 'object' => $attributes[0]['object'], 'list' => $attributes[0]['list'], ]; } // [...] } } @nicolasgrekas

Slide 21

Slide 21 text

class Vimeo { public function __construct( #[AutowireInline( factory: [ScopingHttpClient::class, 'forBaseUri'], arguments: [ '$baseUri' => 'https://api.vimeo.com', '$defaultOptions' => [ 'auth_bearer' => '%env(VIMEO_ACCESS_TOKEN)%', 'headers' => ['Accept: application/vnd.vimeo.*+json'], ] ] )] private HttpClientInterface $vimeo, ) { } @nicolasgrekas

Slide 22

Slide 22 text

class Vimeo { public function __construct( #[AutowireHttpClient( base_uri: 'https://api.vimeo.com', auth_bearer: '%env(VIMEO_ACCESS_TOKEN)%', headers: ['Accept: application/vnd.vimeo.*+json'], )] private HttpClientInterface $vimeo, ) { } @nicolasgrekas

Slide 23

Slide 23 text

public function testNewsLetter() { self::bootKernel(); $container = static::getContainer(); $generator = $container->get(NewsletterGenerator::class); $this->assertSame('...', $generator->generateMonthlyNews()); } @nicolasgrekas Unit or Functional Testing as fit

Slide 24

Slide 24 text

public function testNewsLetter() { self::bootKernel(); $container = static::getContainer(); $repo = $this->createMock(NewsRepositoryInterface::class); $repo->expects(self::once()) ->method('findNewsFromLastMonth') ->willReturn(/* ... */); $container->set(NewsRepositoryInterface::class, $repo); $generator = $container->get(NewsletterGenerator::class); @nicolasgrekas Unit or Functional Testing as fit

Slide 25

Slide 25 text

bin/console cache:clear It's all compiled! @nicolasgrekas

Slide 26

Slide 26 text

Context-free implementations Context-aware declarations You're always in the driving seat What for? @nicolasgrekas

Slide 27

Slide 27 text

Credits @nicolasgrekas

Slide 28

Slide 28 text

Hundreds of contributors @nicolasgrekas

Slide 29

Slide 29 text

symfony.com/slack Mutual aid Kindness CARE team @nicolasgrekas

Slide 30

Slide 30 text

Thank you! See you next at SymfonyCon Amsterdam on November 27-28! @nicolasgrekas