Skip to content

Commit e8ee4ed

Browse files
committed
Merge pull request #1660 from richardmiller/adding_to_di_compilation
Adding more detail to the DI compilation page
2 parents 61ba3f2 + 9542954 commit e8ee4ed

File tree

1 file changed

+217
-31
lines changed

1 file changed

+217
-31
lines changed

components/dependency_injection/compilation.rst

Lines changed: 217 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,220 @@ 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+
use Symfony\Component\Config\Definition\Processor;
144+
// ...
145+
146+
public function load(array $configs, ContainerBuilder $container)
147+
{
148+
$configuration = new Configuration();
149+
$processor = new Processor();
150+
$config = $processor->processConfiguration($configuration, $configs);
151+
152+
$foo = $config['foo']; //fooValue
153+
$bar = $config['bar']; //barValue
154+
155+
// ...
156+
}
157+
158+
There are a further two methods you must implement. One to return the XML
159+
namespace so that the relevant parts of an XML config file are passed to
160+
the extension. The other to specify the base path to XSD files to validate
161+
the XML configuration::
162+
163+
public function getXsdValidationBasePath()
164+
{
165+
return __DIR__.'/../Resources/config/';
166+
}
167+
168+
public function getNamespace()
169+
{
170+
return 'http://www.example.com/symfony/schema/';
171+
}
172+
173+
..note::
174+
XSD validation is optional, returning ``false`` from the ``getXsdValidationBasePath``
175+
method will disable it.
176+
177+
The XML version of the config would then look like this:
178+
179+
.. code-block:: xml
180+
<?xml version="1.0" ?>
181+
182+
<container xmlns="http://symfony.com/schema/dic/services"
183+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
184+
xmlns:acme_demo="http://www.example.com/symfony/schema/"
185+
xsi:schemaLocation="http://www.example.com/symfony/schema/ http://www.example.com/symfony/schema/hello-1.0.xsd">
186+
187+
<acme_demo:config>
188+
<acme_demo:foo>fooValue</acme_hello:foo>
189+
<acme_demo:bar>barValue</acme_demo:bar>
190+
</acme_demo:config>
191+
192+
</container>
193+
194+
..note::
195+
In the Symfony2 full stack framework there is a base Extension class which
196+
implements these methods as well as a short cut method for processing the
197+
configuration. See :doc:`/cookbook/bundles/extension` for more details.
198+
199+
The processed config value can now be added as container parameters as if they were
200+
listed in a ``parameters`` section of the config file but with merging multiple files
201+
and validation of the configuration thrown in::
202+
203+
public function load(array $configs, ContainerBuilder $container)
204+
{
205+
$configuration = new Configuration();
206+
$processor = new Processor();
207+
$config = $processor->processConfiguration($configuration, $configs);
208+
209+
$container->setParameter('acme_demo.FOO', $config['foo'])
210+
211+
// ...
212+
}
213+
214+
More complex configuration requirements can be catered for in the Extension
215+
classes. For example, you may choose to load a main service configuration file
216+
but also load a secondary one only if a certain parameter is set::
217+
218+
public function load(array $configs, ContainerBuilder $container)
219+
{
220+
$configuration = new Configuration();
221+
$processor = new Processor();
222+
$config = $processor->processConfiguration($configuration, $configs);
223+
224+
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
225+
$loader->load('services.xml');
226+
227+
if ($config['advanced']) {
228+
$loader->load('advanced.xml');
229+
}
230+
}
231+
232+
.. note::
233+
234+
If you need to manipulate the configuration loaded by an extension then
235+
you cannot do it from another extension as it uses a fresh container.
236+
You should instead use a compiler pass which works with the full container
237+
after the extensions have been processed.
238+
25239
Creating a Compiler Pass
26240
------------------------
27241

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

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-
116302
Dumping the Configuration for Performance
117303
-----------------------------------------
118304

@@ -134,7 +320,7 @@ configuration. The ``PhpDumper`` makes dumping the compiled container easy::
134320
$container = new ProjectServiceContiner();
135321
} else {
136322
$container = new ContainerBuilder();
137-
//--
323+
// ...
138324
$container->compile();
139325

140326
$dumper = new PhpDumper($container);
@@ -180,7 +366,7 @@ but getting an up to date configuration whilst developing your application::
180366
$container = new MyCachedContainer();
181367
} else {
182368
$container = new ContainerBuilder();
183-
//--
369+
// ...
184370
$container->compile();
185371

186372
if(!$isDebug)
@@ -211,7 +397,7 @@ and use them as metadata for the cache::
211397

212398
if (!$containerConfigCache->isFresh()) {
213399
$containerBuilder = new ContainerBuilder();
214-
//--
400+
// ...
215401
$containerBuilder->compile();
216402

217403
$dumper = new PhpDumper($containerBuilder);

0 commit comments

Comments
 (0)