Slide 1

Slide 1 text

Le DIC ce chef d'orchestre! @AdrienBrault 1

Slide 2

Slide 2 text

About me • Adrien Brault • Software Engineer at Hautelook | Nordstrom Rack • Sensiolabs Certified Symfony Developer Expert • willdurand/Hateoas BazingaHateoasBundle • @AdrienBrault everywhere 2

Slide 3

Slide 3 text

Timeline • Dependency Injection & Symfony Services • ContainerBuilder • Container Compilation • Patterns, Tips and Tricks 3

Slide 4

Slide 4 text

Dependency Injection & Symfony Services 4

Slide 5

Slide 5 text

DIC: Dependency Injection Container 5

Slide 6

Slide 6 text

class UserManager
 {
 private $db;
 
 public function __construct()
 {
 $this->db = new PDO(
 'mysql:host=localhost;dbname=testdb',
 'username',
 'password'
 );
 }
 }
 
 $manager = new UserManager(); % 6

Slide 7

Slide 7 text

class UserManager
 {
 private $db;
 
 public function __construct(
 ContainerInterface $container
 ) {
 $this->db = $container->get('db');
 }
 }
 
 $container = new Container();
 $container->set(
 'db',
 new PDO(
 'mysql:host=localhost;dbname=testdb',
 'username',
 'password'
 )
 );
 $manager = new UserManager($container); % 7

Slide 8

Slide 8 text

class UserManager
 {
 private $db;
 
 public function __construct(PDO $db)
 {
 $this->db = $db;
 }
 }
 
 $manager = new UserManager(
 new PDO(
 'mysql:host=localhost;dbname=testdb',
 'username',
 'password'
 )
 ); & 8

Slide 9

Slide 9 text

Creating services in Symfony 9

Slide 10

Slide 10 text

Write services configuration 
 
 
 
 
 
 
 mysql:host=localhost;dbname=testdb
 username
 password
 10

Slide 11

Slide 11 text

Load the services config # app/config/config.yml
 imports:
 - { resource: "@AppBundle/Resources/config/services.xml" } OR // src/AppBundle/DependencyInjection/AppExtension.php
 class AppExtension extends Extension
 {
 public function load(
 array $configs,
 ContainerBuilder $container
 ) {
 $loader = new Loader\XmlFileLoader(
 $container,
 new FileLocator(
 __DIR__ . '/../Resources/config'
 )
 );
 $loader->load('services.xml');
 }
 } 11

Slide 12

Slide 12 text

Profit $manager = $container->get('user_manager'); 12

Slide 13

Slide 13 text

ContainerBuilder 13

Slide 14

Slide 14 text

Remember this ? // AppBundle/DependencyInjection/AppExtension.php
 
 class AppExtension extends Extension
 {
 public function load(
 array $configs,
 ContainerBuilder $container
 ) {
 $loader = new Loader\XmlFileLoader(
 $container,
 new FileLocator(
 __DIR__ . '/../Resources/config'
 )
 );
 $loader->load('services.xml');
 }
 } 14

Slide 15

Slide 15 text

XML/YAML maps to PHP 
 mysql:host=localhost;dbname=testdb
 username
 password
 
 use Symfony\Component\DependencyInjection\Definition; 
 $container->setDefinition(
 'db',
 new Definition('PDO', [
 'mysql:host=localhost;dbname=testdb',
 'username',
 'password',
 ])
 ); 㱺 15

Slide 16

Slide 16 text

XML/YAML maps to PHP 
 
 use Symfony\Component\DependencyInjection\Reference; 
 $container->setDefinition(
 'user_manager',
 new Definition('UserManager', [
 new Reference('db'),
 ])
 ); 㱺 16

Slide 17

Slide 17 text

The ContainerBuilder and Definitions are mutable $container->removeDefinition('bad_bad_service'); 
 $container
 ->getDefinition('api_client') 
 ->replaceArgument(1, new Reference('another_service')) 
 ->addMethodCall('setLogger', [new Reference('logger')]) 
 ->addTag('form.type', ['alias' => 'wow_such_form']) 
 ->setPublic(false)
 ; 17

Slide 18

Slide 18 text

To learn more about the ContainerBuilder apis • Check out the PHP tab of the service container documentation examples • Symfony\Component\DependencyInjection\ • ContainerBuilder • Definition • Reference • Parameter • DefinitionDecorator • Alias • Scope 18

Slide 19

Slide 19 text

ContainerBuilder is usable standalone $container = new ContainerBuilder();
 $container->setDefinition(...);
 $container->setDefinition(...);
 
 $db = $container->get('db');
 $manager = $container->get('user_manager');
 
 var_dump($db); // object(PDO)#8 (0) {}
 var_dump($manager); // object(UserManager)#9 (0) {} 19

Slide 20

Slide 20 text

Container Building Workflow ContainerBuilder app/cache/dev/ appDevDebugProjectContainer.php 20

Slide 21

Slide 21 text

class appDevDebugProjectContainer extends Container
 {
 public function __construct()
 {
 // ...
 
 $this->methodMap = array(
 'form.resolved_type_factory' => 'getForm_ResolvedTypeFactoryService',
 );
 }
 
 protected function getForm_ResolvedTypeFactoryService()
 {
 return $this->services['form.resolved_type_factory'] =
 new ResolvedTypeFactoryDataCollectorProxy(
 new ResolvedFormTypeFactory(),
 $this->get('data_collector.form')
 )
 ;
 }
 
 // ...
 } 21

Slide 22

Slide 22 text

Container compilation 22

Slide 23

Slide 23 text

Compilation Flow 1. Register bundles' DI extension 2. Let bundles add CompilerPasses 3. Read app/config/config.yml 4. Run CompilerPasses 4.1. First CompilerPass calls the DI extensions 4.2. All the other CompilerPasses run 23

Slide 24

Slide 24 text

DI extensions interface ExtensionInterface
 {
 public function load( array $configs, ContainerBuilder $container );
 } • Sandboxed: the given ContainerBuilder is always empty (aside from the base parameters, like kernel.debug) • Can change its behaviour using the provided configuration 24

Slide 25

Slide 25 text

CompilerPasses interface CompilerPassInterface
 {
 public function process(ContainerBuilder $container);
 } • Runs right before dumping the ContainerBuilder to PHP • Can change anything • A few Symfony ones optimise the ContainerBuilder • Most others allow interaction across bundles using tags 25

Slide 26

Slide 26 text

Adding compiler passes 
 class AppBundle extends Bundle
 {
 public function build(ContainerBuilder $container)
 {
 $container->addCompilerPass(
 new DogeCompilerPass()
 );
 
 parent::build($container);
 }
 } 26

Slide 27

Slide 27 text

class RestCompilerPass implements CompilerPassInterface
 {
 public function process(ContainerBuilder $container)
 {
 if (!$container->hasDefinition('jms_serializer')) {
 $container->removeDefinition(
 'rest.jms_event_subscriber'
 );
 }
 }
 } 27

Slide 28

Slide 28 text

class BlackHoleCompilerPass implements CompilerPassInterface
 {
 public function process(ContainerBuilder $container)
 {
 foreach ($container->getServiceIds() as $id) {
 $container->removeDefinition($id);
 }
 }
 } 28

Slide 29

Slide 29 text

Learn more about the compilation http://symfony.com/doc/current/components/ dependency_injection/compilation.html 29

Slide 30

Slide 30 text

Patterns, Tips and Tricks 30

Slide 31

Slide 31 text

Anonymous services 
 
 
 mysql:wow
 username
 password
 
 
 31

Slide 32

Slide 32 text

JSON/CSV injection at compilation 
 use Symfony\Component\Config\Resource\FileResource;
 
 class MemeExtension extends Extension
 {
 public function load(
 array $config,
 ContainerBuilder $container
 ) {
 // load services from XML/YAML
 
 $memesPath = __DIR__ . '/../Resources/config/memes.json';
 $memes = json_decode(file_get_contents($memesPath));
 
 $container
 ->getDefinition('meme_generator')
 ->replaceArgument(0, $memes)
 ;
 $container->addResource(new FileResource($memesPath));
 }
 } 32

Slide 33

Slide 33 text

PrependExtension class RestExtension extends Extension
 implements PrependExtensionInterface
 {
 public function load(array $config, ContainerBuilder $container) { }
 
 public function prepend(ContainerBuilder $container)
 {
 $container->prependExtensionConfig(
 'framework',
 ['serializer' => 'true']
 );
 }
 } http://symfony.com/doc/current/cookbook/bundles/ prepend_extension.html 33

Slide 34

Slide 34 text

Tags class NotificationSenderCompilerPass
 implements CompilerPassInterface
 {
 public function process(ContainerBuilder $container)
 {
 $chainDefinition = $container
 ->getDefinition('notification_chain');
 
 $services = $container
 ->findTaggedServiceIds('notification_sender');
 foreach ($services as $id => $tags) {
 foreach ($tags as $tagAttributes) {
 $chainDefinition->addMethodCall(
 'addSender',
 [new Reference($id)]
 );
 }
 }
 }
 } 34

Slide 35

Slide 35 text


 
 
 
 
 
 
 
 
 http://symfony.com/doc/current/components/ dependency_injection/tags.html 35 㱺 after the compiler pass

Slide 36

Slide 36 text

Use semantic configuration, not parameters (for reusable bundles ) class Configuration implements ConfigurationInterface
 {
 public function getConfigTreeBuilder()
 {
 $treeBuilder = new TreeBuilder();
 $rootNode = $treeBuilder->root('api');
 
 $rootNode
 ->children()
 ->arrayNode('rate_limit')
 ->canBeEnabled()
 ->children()
 ->integerNode('hourly_limit')->end()
 ->end()
 ->end()
 ->end()
 ;
 
 return $treeBuilder;
 }
 } 36

Slide 37

Slide 37 text

class ApiExtension extends Extension
 {
 public function load(
 array $configs,
 ContainerBuilder $container
 ) {
 $config = $this->processConfiguration(
 new Configuration(),
 $configs
 );
 
 $loader = ...;
 
 if ($config['api']['rate_limit']['enabled']) {
 $loader->load('rate_limit.xml');
 }
 }
 } http://symfony.com/doc/current/cookbook/bundles/ configuration.html 37

Slide 38

Slide 38 text

Automatic configuration processing use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
 
 class ApiExtension extends ConfigurableExtension
 {
 protected function loadInternal(
 array $config,
 ContainerBuilder $container
 ) {
 if ($config['api']['rate_limit']['enabled']) {
 // ...
 }
 }
 } http://symfony.com/doc/current/cookbook/bundles/ configuration.html 38

Slide 39

Slide 39 text

container:debug 39

Slide 40

Slide 40 text

container:debug --parameters 40

Slide 41

Slide 41 text

container:debug --tags 41

Slide 42

Slide 42 text

container:debug service 42

Slide 43

Slide 43 text

config:dump 43

Slide 44

Slide 44 text

Thanks! • https://speakerdeck.com/adrienbrault/le-dic-ce- chef-dorchestre • Questions ❓ 44