Skip to content

Commit 7c69432

Browse files
Adding more detail to the DI compilation page
1 parent 57d9c48 commit 7c69432

File tree

1 file changed

+204
-28
lines changed

1 file changed

+204
-28
lines changed

components/dependency_injection/compilation.rst

Lines changed: 204 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,210 @@ validity, further compiler passes are used to optimize the configuration
2222
before it is cached. For example, private services and abstract services
2323
are removed, and aliases are resolved.
2424

25+
Managing Configuration with Extensions
26+
--------------------------------------
27+
28+
As well as loading configuration directly into the container as shown in
29+
:doc:`/components/dependency_injection/introduction`, you can manage it by
30+
registering extensions with the container. The first step in the compilation
31+
process is to load configuration from any extension classes registered with
32+
the container. Unlike the configuration loaded directly they are only processed
33+
when the container is compiled. If your application is modular then extensions
34+
allow each module to register and manage their own service configuration.
35+
36+
The extensions must implement :class:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface`
37+
and can be registered with the container with::
38+
39+
$container->registerExtension($extension);
40+
41+
The main work of the extension is done in the ``load`` method. In the load method
42+
you can load configuration from one or more configuration files as well as
43+
manipulate the container definitions using the methods shown in :doc:`/components/dependency_injection/definitions`.
44+
45+
The ``load`` method is passed a fresh container to set up, which is then
46+
merged afterwards into the container it is registered with. This allows you
47+
to have several extensions managing container definitions independently.
48+
The extensions do not add to the containers configuration when they are added
49+
but are processed when the container's ``compile`` method is called.
50+
51+
A very simple extension may just load configuration files into the container::
52+
53+
use Symfony\Component\DependencyInjection\ContainerBuilder;
54+
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
55+
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
56+
use Symfony\Component\Config\FileLocator;
57+
58+
class AcmeDemoExtension implements ExtensionInterface
59+
{
60+
public function load(array $configs, ContainerBuilder $container)
61+
{
62+
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
63+
$loader->load('services.xml');
64+
}
65+
66+
//--
67+
}
68+
69+
This does not gain very much compared to loading the file directly into the
70+
overall container being built. It just allows the files to be split up amongst
71+
the modules/bundles. Being able to affect the configuration of a module from
72+
configuration files outside of the module/bundle is needed to make a complex
73+
application configurable. This can be done by specifying sections of config files
74+
loaded directly into the container as being for a particular extension. These
75+
sections on the config will not be processed directly by the container but by the
76+
relevant Extension.
77+
78+
The Extension must specify a ``getAlias`` method to implement the interface::
79+
80+
//--
81+
82+
class AcmeDemoExtension implements ExtensionInterface
83+
{
84+
//--
85+
86+
public function getAlias()
87+
{
88+
return 'acme_demo';
89+
}
90+
}
91+
92+
For YAML configuration files specifying the alias for the Extension as a key
93+
will mean that those values are passed to the Extension's ``load`` method:
94+
95+
.. code-block:: yaml
96+
#--
97+
98+
acme_demo:
99+
foo: fooValue
100+
bar: barValue
101+
102+
If this file is loaded into the configuration then the values in it are only
103+
processed when the container is compiled at which point the Extensions are loaded::
104+
105+
use Symfony\Component\DependencyInjection\ContainerBuilder;
106+
use Symfony\Component\Config\FileLocator;
107+
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
108+
109+
$container = new ContainerBuilder();
110+
$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
111+
$loader->load('config.yml');
112+
113+
$container->registerExtension(new AcmeDemoExtension);
114+
//--
115+
$container->compile();
116+
117+
The values from those sections of the config files are passed into the first
118+
argument of the ``load`` method of the extension::
119+
120+
public function load(array $configs, ContainerBuilder $container)
121+
{
122+
$foo = $configs[0]['foo']; //fooValue
123+
$bar = $configs[0]['bar']; //barValue
124+
}
125+
126+
The ``$configs`` argument is an array containing each different config file
127+
that was loaded into the container. You are only loading a single config file
128+
in the above example but it will still be within an array. The array will look
129+
like this::
130+
131+
array(
132+
array(
133+
'foo' => 'fooValue',
134+
'bar' => 'barValue',
135+
)
136+
)
137+
138+
Whilst you can manually manage merging the different files, it is much better
139+
to use :doc:`the Config Component</components/config/introduction>` to merge
140+
and validate the config values. Using the configuration processing you could
141+
access the config value this way::
142+
143+
public function load(array $configs, ContainerBuilder $container)
144+
{
145+
$configuration = new Configuration();
146+
$config = $this->processConfiguration($configuration, $configs);
147+
148+
$foo = $config['foo']; //fooValue
149+
$bar = $config['bar']; //barValue
150+
151+
//--
152+
}
153+
154+
There are a further two methods you must implement. One to return the XML
155+
namespace so that the relevant parts of an XML config file are passed to
156+
the extension. The other to specify the base path to XSD files to validate
157+
the XML configuration::
158+
159+
public function getXsdValidationBasePath()
160+
{
161+
return __DIR__.'/../Resources/config/';
162+
}
163+
164+
public function getNamespace()
165+
{
166+
return 'http://www.example.com/symfony/schema/';
167+
}
168+
169+
The XML version of the config would then look like this:
170+
171+
.. code-block:: xml
172+
<?xml version="1.0" ?>
173+
174+
<container xmlns="http://symfony.com/schema/dic/services"
175+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
176+
xmlns:acme_demo="http://www.example.com/symfony/schema/"
177+
xsi:schemaLocation="http://www.example.com/symfony/schema/ http://www.example.com/symfony/schema/hello-1.0.xsd">
178+
179+
<acme_demo:config>
180+
<acme_demo:foo>fooValue</acme_hello:foo>
181+
<acme_demo:bar>barValue</acme_demo:bar>
182+
</acme_demo:config>
183+
184+
</container>
185+
186+
..note::
187+
In the Symfony2 full stack framework there is a base Extension class which
188+
implements these methods. See :doc:`/cookbook/bundles/extension` for
189+
more details.
190+
191+
The processed config value can now be added as container parameters as if they were
192+
listed in a ``parameters`` section of the config file but with merging multiple files
193+
and validation of the configuration thrown in::
194+
195+
public function load(array $configs, ContainerBuilder $container)
196+
{
197+
$configuration = new Configuration();
198+
$config = $this->processConfiguration($configuration, $configs);
199+
200+
$container->setParameter('acme_demo.FOO', $config['foo'])
201+
202+
//--
203+
}
204+
205+
More complex configuration requirements can be catered for in the Extension
206+
classes. For example, you may choose to load a main service configuration file
207+
but also load a secondary one only if a certain parameter is set::
208+
209+
public function load(array $configs, ContainerBuilder $container)
210+
{
211+
$configuration = new Configuration();
212+
$config = $this->processConfiguration($configuration, $configs);
213+
214+
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
215+
$loader->load('services.xml');
216+
217+
if ($config['advanced']) {
218+
$loader->load('advanced.xml');
219+
}
220+
}
221+
222+
.. note::
223+
224+
If you need to manipulate the configuration loaded by an extension then
225+
you cannot do it from another extension as it uses a fresh container.
226+
You should instead use a compiler pass which works with the full container
227+
after the extensions have been processed.
228+
25229
Creating a Compiler Pass
26230
------------------------
27231

@@ -85,34 +289,6 @@ For example, to run your custom pass after the default removal passes have been
85289
$container = new ContainerBuilder();
86290
$container->addCompilerPass(new CustomCompilerPass, PassConfig::TYPE_AFTER_REMOVING);
87291

88-
89-
Managing Configuration with Extensions
90-
--------------------------------------
91-
92-
As well as loading configuration directly into the container as shown in
93-
:doc:`/components/dependency_injection/introduction`, you can manage it by registering
94-
extensions with the container. The extensions must implement :class:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface`
95-
and can be registered with the container with::
96-
97-
$container->registerExtension($extension);
98-
99-
The main work of the extension is done in the ``load`` method. In the load method
100-
you can load configuration from one or more configuration files as well as
101-
manipulate the container definitions using the methods shown in :doc:`/components/dependency_injection/definitions`.
102-
103-
The ``load`` method is passed a fresh container to set up, which is then
104-
merged afterwards into the container it is registered with. This allows you
105-
to have several extensions managing container definitions independently.
106-
The extensions do not add to the containers configuration when they are added
107-
but are processed when the container's ``compile`` method is called.
108-
109-
.. note::
110-
111-
If you need to manipulate the configuration loaded by an extension then
112-
you cannot do it from another extension as it uses a fresh container.
113-
You should instead use a compiler pass which works with the full container
114-
after the extensions have been processed.
115-
116292
Dumping the Configuration for Performance
117293
-----------------------------------------
118294

0 commit comments

Comments
 (0)