From 7c69432bfe33a0907adabaadf8277da9399d3547 Mon Sep 17 00:00:00 2001 From: Richard Miller Date: Tue, 28 Aug 2012 16:51:35 +0100 Subject: [PATCH 1/4] Adding more detail to the DI compilation page --- .../dependency_injection/compilation.rst | 232 +++++++++++++++--- 1 file changed, 204 insertions(+), 28 deletions(-) diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst index 1cc8141f9ee..12195669137 100644 --- a/components/dependency_injection/compilation.rst +++ b/components/dependency_injection/compilation.rst @@ -22,6 +22,210 @@ validity, further compiler passes are used to optimize the configuration before it is cached. For example, private services and abstract services are removed, and aliases are resolved. +Managing Configuration with Extensions +-------------------------------------- + +As well as loading configuration directly into the container as shown in +:doc:`/components/dependency_injection/introduction`, you can manage it by +registering extensions with the container. The first step in the compilation +process is to load configuration from any extension classes registered with +the container. Unlike the configuration loaded directly they are only processed +when the container is compiled. If your application is modular then extensions +allow each module to register and manage their own service configuration. + +The extensions must implement :class:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface` +and can be registered with the container with:: + + $container->registerExtension($extension); + +The main work of the extension is done in the ``load`` method. In the load method +you can load configuration from one or more configuration files as well as +manipulate the container definitions using the methods shown in :doc:`/components/dependency_injection/definitions`. + +The ``load`` method is passed a fresh container to set up, which is then +merged afterwards into the container it is registered with. This allows you +to have several extensions managing container definitions independently. +The extensions do not add to the containers configuration when they are added +but are processed when the container's ``compile`` method is called. + +A very simple extension may just load configuration files into the container:: + + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; + use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; + use Symfony\Component\Config\FileLocator; + + class AcmeDemoExtension implements ExtensionInterface + { + public function load(array $configs, ContainerBuilder $container) + { + $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('services.xml'); + } + + //-- + } + +This does not gain very much compared to loading the file directly into the +overall container being built. It just allows the files to be split up amongst +the modules/bundles. Being able to affect the configuration of a module from +configuration files outside of the module/bundle is needed to make a complex +application configurable. This can be done by specifying sections of config files +loaded directly into the container as being for a particular extension. These +sections on the config will not be processed directly by the container but by the +relevant Extension. + +The Extension must specify a ``getAlias`` method to implement the interface:: + + //-- + + class AcmeDemoExtension implements ExtensionInterface + { + //-- + + public function getAlias() + { + return 'acme_demo'; + } + } + +For YAML configuration files specifying the alias for the Extension as a key +will mean that those values are passed to the Extension's ``load`` method: + +.. code-block:: yaml + #-- + + acme_demo: + foo: fooValue + bar: barValue + +If this file is loaded into the configuration then the values in it are only +processed when the container is compiled at which point the Extensions are loaded:: + + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Component\Config\FileLocator; + use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(__DIR__)); + $loader->load('config.yml'); + + $container->registerExtension(new AcmeDemoExtension); + //-- + $container->compile(); + +The values from those sections of the config files are passed into the first +argument of the ``load`` method of the extension:: + + public function load(array $configs, ContainerBuilder $container) + { + $foo = $configs[0]['foo']; //fooValue + $bar = $configs[0]['bar']; //barValue + } + +The ``$configs`` argument is an array containing each different config file +that was loaded into the container. You are only loading a single config file +in the above example but it will still be within an array. The array will look +like this:: + + array( + array( + 'foo' => 'fooValue', + 'bar' => 'barValue', + ) + ) + +Whilst you can manually manage merging the different files, it is much better +to use :doc:`the Config Component` to merge +and validate the config values. Using the configuration processing you could +access the config value this way:: + + public function load(array $configs, ContainerBuilder $container) + { + $configuration = new Configuration(); + $config = $this->processConfiguration($configuration, $configs); + + $foo = $config['foo']; //fooValue + $bar = $config['bar']; //barValue + + //-- + } + +There are a further two methods you must implement. One to return the XML +namespace so that the relevant parts of an XML config file are passed to +the extension. The other to specify the base path to XSD files to validate +the XML configuration:: + + public function getXsdValidationBasePath() + { + return __DIR__.'/../Resources/config/'; + } + + public function getNamespace() + { + return 'http://www.example.com/symfony/schema/'; + } + +The XML version of the config would then look like this: + +.. code-block:: xml + + + + + + fooValue + barValue + + + + +..note:: + In the Symfony2 full stack framework there is a base Extension class which + implements these methods. See :doc:`/cookbook/bundles/extension` for + more details. + +The processed config value can now be added as container parameters as if they were +listed in a ``parameters`` section of the config file but with merging multiple files +and validation of the configuration thrown in:: + + public function load(array $configs, ContainerBuilder $container) + { + $configuration = new Configuration(); + $config = $this->processConfiguration($configuration, $configs); + + $container->setParameter('acme_demo.FOO', $config['foo']) + + //-- + } + +More complex configuration requirements can be catered for in the Extension +classes. For example, you may choose to load a main service configuration file +but also load a secondary one only if a certain parameter is set:: + + public function load(array $configs, ContainerBuilder $container) + { + $configuration = new Configuration(); + $config = $this->processConfiguration($configuration, $configs); + + $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('services.xml'); + + if ($config['advanced']) { + $loader->load('advanced.xml'); + } + } + +.. note:: + + If you need to manipulate the configuration loaded by an extension then + you cannot do it from another extension as it uses a fresh container. + You should instead use a compiler pass which works with the full container + after the extensions have been processed. + Creating a Compiler Pass ------------------------ @@ -85,34 +289,6 @@ For example, to run your custom pass after the default removal passes have been $container = new ContainerBuilder(); $container->addCompilerPass(new CustomCompilerPass, PassConfig::TYPE_AFTER_REMOVING); - -Managing Configuration with Extensions --------------------------------------- - -As well as loading configuration directly into the container as shown in -:doc:`/components/dependency_injection/introduction`, you can manage it by registering -extensions with the container. The extensions must implement :class:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface` -and can be registered with the container with:: - - $container->registerExtension($extension); - -The main work of the extension is done in the ``load`` method. In the load method -you can load configuration from one or more configuration files as well as -manipulate the container definitions using the methods shown in :doc:`/components/dependency_injection/definitions`. - -The ``load`` method is passed a fresh container to set up, which is then -merged afterwards into the container it is registered with. This allows you -to have several extensions managing container definitions independently. -The extensions do not add to the containers configuration when they are added -but are processed when the container's ``compile`` method is called. - -.. note:: - - If you need to manipulate the configuration loaded by an extension then - you cannot do it from another extension as it uses a fresh container. - You should instead use a compiler pass which works with the full container - after the extensions have been processed. - Dumping the Configuration for Performance ----------------------------------------- From 5702a9bffe148c1bb862be88a186ba2bafbfff82 Mon Sep 17 00:00:00 2001 From: Richard Miller Date: Tue, 28 Aug 2012 17:08:38 +0100 Subject: [PATCH 2/4] Updating to remove HTTPKernel specific method from di config processing examples --- components/dependency_injection/compilation.rst | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst index 12195669137..0a86cd4e3cb 100644 --- a/components/dependency_injection/compilation.rst +++ b/components/dependency_injection/compilation.rst @@ -140,10 +140,14 @@ to use :doc:`the Config Component` to merge and validate the config values. Using the configuration processing you could access the config value this way:: + use Symfony\Component\Config\Definition\Processor; + //-- + public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); - $config = $this->processConfiguration($configuration, $configs); + $processor = new Processor(); + $config = $processor->processConfiguration($configuration, $configs); $foo = $config['foo']; //fooValue $bar = $config['bar']; //barValue @@ -185,8 +189,8 @@ The XML version of the config would then look like this: ..note:: In the Symfony2 full stack framework there is a base Extension class which - implements these methods. See :doc:`/cookbook/bundles/extension` for - more details. + implements these methods as well as a short cut method for processing the + configuration. See :doc:`/cookbook/bundles/extension` for more details. The processed config value can now be added as container parameters as if they were listed in a ``parameters`` section of the config file but with merging multiple files @@ -195,7 +199,8 @@ and validation of the configuration thrown in:: public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); - $config = $this->processConfiguration($configuration, $configs); + $processor = new Processor(); + $config = $processor->processConfiguration($configuration, $configs); $container->setParameter('acme_demo.FOO', $config['foo']) @@ -209,7 +214,8 @@ but also load a secondary one only if a certain parameter is set:: public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); - $config = $this->processConfiguration($configuration, $configs); + $processor = new Processor(); + $config = $processor->processConfiguration($configuration, $configs); $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.xml'); From 15e8e87aece7be34eaf931be6a8ad47b742e5926 Mon Sep 17 00:00:00 2001 From: Richard Miller Date: Tue, 28 Aug 2012 17:11:51 +0100 Subject: [PATCH 3/4] Adding a note that XSD validation of config files is optional --- components/dependency_injection/compilation.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst index 0a86cd4e3cb..92623f0f3ce 100644 --- a/components/dependency_injection/compilation.rst +++ b/components/dependency_injection/compilation.rst @@ -170,6 +170,10 @@ the XML configuration:: return 'http://www.example.com/symfony/schema/'; } +..note:: + XSD validation is optional, returning ``false`` from the ``getXsdValidationBasePath`` + method will disable it. + The XML version of the config would then look like this: .. code-block:: xml From 954295442da89e1d3e8b85bb2c9b3e988f8e8228 Mon Sep 17 00:00:00 2001 From: Richard Miller Date: Tue, 28 Aug 2012 17:59:18 +0100 Subject: [PATCH 4/4] Changing syntax used for missing code sections --- .../dependency_injection/compilation.rst | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/components/dependency_injection/compilation.rst b/components/dependency_injection/compilation.rst index 92623f0f3ce..b0c1778f683 100644 --- a/components/dependency_injection/compilation.rst +++ b/components/dependency_injection/compilation.rst @@ -63,7 +63,7 @@ A very simple extension may just load configuration files into the container:: $loader->load('services.xml'); } - //-- + // ... } This does not gain very much compared to loading the file directly into the @@ -77,11 +77,11 @@ relevant Extension. The Extension must specify a ``getAlias`` method to implement the interface:: - //-- + // ... class AcmeDemoExtension implements ExtensionInterface { - //-- + // ... public function getAlias() { @@ -93,7 +93,7 @@ For YAML configuration files specifying the alias for the Extension as a key will mean that those values are passed to the Extension's ``load`` method: .. code-block:: yaml - #-- + # ... acme_demo: foo: fooValue @@ -111,7 +111,7 @@ processed when the container is compiled at which point the Extensions are loade $loader->load('config.yml'); $container->registerExtension(new AcmeDemoExtension); - //-- + // ... $container->compile(); The values from those sections of the config files are passed into the first @@ -141,7 +141,7 @@ and validate the config values. Using the configuration processing you could access the config value this way:: use Symfony\Component\Config\Definition\Processor; - //-- + // ... public function load(array $configs, ContainerBuilder $container) { @@ -152,7 +152,7 @@ access the config value this way:: $foo = $config['foo']; //fooValue $bar = $config['bar']; //barValue - //-- + // ... } There are a further two methods you must implement. One to return the XML @@ -208,7 +208,7 @@ and validation of the configuration thrown in:: $container->setParameter('acme_demo.FOO', $config['foo']) - //-- + // ... } More complex configuration requirements can be catered for in the Extension @@ -320,7 +320,7 @@ configuration. The ``PhpDumper`` makes dumping the compiled container easy:: $container = new ProjectServiceContiner(); } else { $container = new ContainerBuilder(); - //-- + // ... $container->compile(); $dumper = new PhpDumper($container); @@ -366,7 +366,7 @@ but getting an up to date configuration whilst developing your application:: $container = new MyCachedContainer(); } else { $container = new ContainerBuilder(); - //-- + // ... $container->compile(); if(!$isDebug) @@ -397,7 +397,7 @@ and use them as metadata for the cache:: if (!$containerConfigCache->isFresh()) { $containerBuilder = new ContainerBuilder(); - //-- + // ... $containerBuilder->compile(); $dumper = new PhpDumper($containerBuilder);