From c9fe05b3b1a03fa196e80b22472eb3a7eb0be0a5 Mon Sep 17 00:00:00 2001 From: Wouter Date: Fri, 30 May 2014 15:53:54 +0200 Subject: [PATCH] Rewrote Extension & Configuration docs --- components/config/definition.rst | 4 + contributing/code/standards.rst | 4 + cookbook/bundles/configuration.rst | 399 +++++++++++++++++++ cookbook/bundles/extension.rst | 611 +++-------------------------- cookbook/bundles/index.rst | 1 + cookbook/map.rst.inc | 1 + 6 files changed, 468 insertions(+), 552 deletions(-) create mode 100644 cookbook/bundles/configuration.rst diff --git a/components/config/definition.rst b/components/config/definition.rst index e62c930b0ed..f16c35d7d60 100644 --- a/components/config/definition.rst +++ b/components/config/definition.rst @@ -391,6 +391,8 @@ with ``append()``:: This is also useful to help you avoid repeating yourself if you have sections of the config that are repeated in different places. +.. _component-config-normalization: + Normalization ------------- @@ -445,6 +447,8 @@ a second argument:: ->fixXmlConfig('child', 'children') ->children() ->arrayNode('children') + // ... + ->end() ->end() ; diff --git a/contributing/code/standards.rst b/contributing/code/standards.rst index 8b4535b0d55..ecff6254849 100644 --- a/contributing/code/standards.rst +++ b/contributing/code/standards.rst @@ -148,9 +148,13 @@ Service Naming Conventions ~~~~~~~~~~~~~~~~~~~~~~~~~~ * A service name contains groups, separated by dots; + * The DI alias of the bundle is the first group (e.g. ``fos_user``); + * Use lowercase letters for service and parameter names; + * A group name uses the underscore notation; + * Each service has a corresponding parameter containing the class name, following the ``SERVICE NAME.class`` convention. diff --git a/cookbook/bundles/configuration.rst b/cookbook/bundles/configuration.rst new file mode 100644 index 00000000000..9d374d2dc4d --- /dev/null +++ b/cookbook/bundles/configuration.rst @@ -0,0 +1,399 @@ +.. index:: + single: Configuration; Semantic + single: Bundle; Extension configuration + +How to Create Friendly Configuration for a Bundle +================================================= + +If you open your application configuration file (usually ``app/config/config.yml``), +you'll see a number of different configuration "namespaces", such as ``framework``, +``twig`` and ``doctrine``. Each of these configures a specific bundle, allowing +you to configure things at a high level and then let the bundle make all the +low-level, complex changes based on your settings. + +For example, the following tells the FrameworkBundle to enable the form +integration, which involves the definition of quite a few services as well +as integration of other related components: + +.. configuration-block:: + + .. code-block:: yaml + + framework: + form: true + + .. code-block:: xml + + + + + + + + + + .. code-block:: php + + $container->loadFromExtension('framework', array( + 'form' => true, + )); + +.. sidebar:: Using Parameters to Configure your Bundle + + If you don't have plans to share your bundle between projects, it doesn't + make sense to use this more advanced way of configuration. Since you use + the bundle only in one project, you can just change the service + configuration each time. + + If you *do* want to be able to configure something from within + ``config.yml``, you can always create a parameter there and use that + parameter somewhere else. + +Using the Bundle Extension +-------------------------- + +The basic idea is that instead of having the user override individual +parameters, you let the user configure just a few, specifically created, +options. As the bundle developer, you then parse through that configuration and +load correct services and parameters inside an "Extension" class. + +As an example, imagine you are creating a social bundle, which provides +integration with Twitter and such. To be able to reuse your bundle, you have to +make the ``client_id`` and ``client_secret`` variables configurable. Your +bundle configuration would look like: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/config.yml + acme_social: + twitter: + client_id: 123 + client_secret: $ecret + + .. code-block:: xml + + + + + + + + + + + + + + .. code-block:: php + + // app/config/config.php + $container->loadFromExtension('acme_social', array( + 'client_id' => 123, + 'client_secret' => '$ecret', + )); + +.. seealso:: + + Read more about the extension in :doc:`/cookbook/bundles/extension`. + +.. tip:: + + If a bundle provides an Extension class, then you should *not* generally + override any service container parameters from that bundle. The idea + is that if an Extension class is present, every setting that should be + configurable should be present in the configuration made available by + that class. In other words, the extension class defines all the public + configuration settings for which backward compatibility will be maintained. + +.. seealso:: + + For parameter handling within a Dependency Injection class see + :doc:`/cookbook/configuration/using_parameters_in_dic`. + + +Processing the ``$configs`` Array +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +First things first, you have to create an extension class as explained in +:doc:`extension`. + +Whenever a user includes the ``acme_social`` key (which is the DI alias) in a +configuration file, the configuration under it is added to an array of +configurations and passed to the ``load()`` method of your extension (Symfony2 +automatically converts XML and YAML to an array). + +For the configuration example in the previous section, the array passed to your +``load()`` method will look like this:: + + array( + array( + 'twitter' => array( + 'client_id' => 123, + 'client_secret' => '$ecret', + ), + ), + ) + +Notice that this is an *array of arrays*, not just a single flat array of the +configuration values. This is intentional, as it allows Symfony to parse +several configuration resources. For example, if ``acme_social`` appears in +another configuration file - say ``config_dev.yml`` - with different values +beneath it, the incoming array might look like this:: + + array( + // values from config.yml + array( + 'twitter' => array( + 'client_id' => 123, + 'client_secret' => '$secret', + ), + ), + // values from config_dev.yml + array( + 'twitter' => array( + 'client_id' => 456, + ), + ), + ) + +The order of the two arrays depends on which one is set first. + +But don't worry! Symfony's Config component will help you merge these values, +provide defaults and give the user validation errors on bad configuration. +Here's how it works. Create a ``Configuration`` class in the +``DependencyInjection`` directory and build a tree that defines the structure +of your bundle's configuration. + +The ``Configuration`` class to handle the sample configuration looks like:: + + // src/Acme/SocialBundle/DependencyInjection/Configuration.php + namespace Acme\SocialBundle\DependencyInjection; + + use Symfony\Component\Config\Definition\Builder\TreeBuilder; + use Symfony\Component\Config\Definition\ConfigurationInterface; + + class Configuration implements ConfigurationInterface + { + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('acme_social'); + + $rootNode + ->children() + ->arrayNode('twitter') + ->children() + ->integerNode('client_id')->end() + ->scalarNode('client_secret')->end() + ->end() + ->end() // twitter + ->end() + ; + + return $treeBuilder; + } + } + +.. seealso:: + + The ``Configuration`` class can be much more complicated than shown here, + supporting "prototype" nodes, advanced validation, XML-specific normalization + and advanced merging. You can read more about this in + :doc:`the Config component documentation `. You + can also see it in action by checking out some of the core Configuration + classes, such as the one from the `FrameworkBundle Configuration`_ or the + `TwigBundle Configuration`_. + +This class can now be used in your ``load()`` method to merge configurations and +force validation (e.g. if an additional option was passed, an exception will be +thrown):: + + public function load(array $configs, ContainerBuilder $container) + { + $configuration = new Configuration(); + + $config = $this->processConfiguration($configuration, $configs); + // ... + } + +The ``processConfiguration()`` method uses the configuration tree you've defined +in the ``Configuration`` class to validate, normalize and merge all of the +configuration arrays together. + +.. tip:: + + Instead of calling ``processConfiguration()`` in your extension each time you + provide some configuration options, you might want to use the + :class:`Symfony\\Component\\HttpKernel\\DependencyInjection\\ConfigurableExtension` + to do this automatically for you:: + + // src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php + namespace Acme\HelloBundle\DependencyInjection; + + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension; + + class AcmeHelloExtension extends ConfigurableExtension + { + // note that this method is called loadInternal and not load + protected function loadInternal(array $mergedConfig, ContainerBuilder $container) + { + // ... + } + } + + This class uses the ``getConfiguration()`` method to get the Configuration + instance, you should override it if your Configuration class is not called + ``Configuration`` or if it is not placed in the same namespace as the + extension. + +.. sidebar:: Processing the Configuration yourself + + Using the Config component is fully optional. The ``load()`` method gets an + array of configuration values. You can simply parse these arrays yourself + (e.g. by overriding configurations and using :phpfunction:`isset` to check + for the existence of a value). Be aware that it'll be very hard to support XML. + + .. code-block:: php + + public function load(array $configs, ContainerBuilder $container) + { + $config = array(); + // let resources override the previous set value + foreach ($configs as $subConfig) { + $config = array_merge($config, $subConfig); + } + + // ... now use the flat $config array + } + +Modifying the Configuration of Another Bundle +--------------------------------------------- + +If you have multiple bundles that depend on each other, it may be useful +to allow one ``Extension`` class to modify the configuration passed to another +bundle's ``Extension`` class, as if the end-developer has actually placed that +configuration in their ``app/config/config.yml`` file. This can be achieved +using a prepend extension. For more details, see +:doc:`/cookbook/bundles/prepend_extension`. + +Dump the Configuration +---------------------- + +The ``config:dump-reference`` command dumps the default configuration of a +bundle in the console using the Yaml format. + +As long as your bundle's configuration is located in the standard location +(``YourBundle\DependencyInjection\Configuration``) and does not require +arguments to be passed to the constructor it will work automatically. If you +have something different, your ``Extension`` class must override the +:method:`Extension::getConfiguration() ` +method and return an instance of your ``Configuration``. + +Supporting XML +-------------- + +Symfony allows people to provide the configuration in three different formats: +Yaml, XML and PHP. Both Yaml and PHP use the same syntax and are supported by +default when using the Config component. Supporting XML requires you to do some +more things. But when sharing your bundle with others, it is recommended that +you follow these steps. + +Make your Config Tree ready for XML +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Config component provides some methods by default to allow it to correctly +process XML configuration. See ":ref:`component-config-normalization`" of the +component documentation. However, you can do some optional things as well, this +will improve the experience of using XML configuration: + +Choosing an XML Namespace +~~~~~~~~~~~~~~~~~~~~~~~~~ + +In XML, the `XML namespace`_ is used to determine which elements belong to the +configuration of a specific bundle. The namespace is returned from the +:method:`Extension::getNamespace() ` +method. By convention, the namespace is a URL (it doesn't have to be a valid +URL nor does it need to exists). By default, the namespace for a bundle is +``http://example.org/dic/schema/DI_ALIAS``, where ``DI_ALIAS`` is the DI alias of +the extension. You might want to change this to a more professional URL:: + + // src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php + + // ... + class AcmeHelloExtension extends Extension + { + // ... + + public function getNamespace() + { + return 'http://acme_company.com/schema/dic/hello'; + } + } + +Providing an XML Schema +~~~~~~~~~~~~~~~~~~~~~~~ + +XML has a very useful feature called `XML schema`_. This allows you to +describe all possible elements and attributes and their values in an XML Schema +Definition (an xsd file). This XSD file is used by IDEs for auto completion and +it is used by the Config component to validate the elements. + +In order to use the schema, the XML configuration file must provide an +``xsi:schemaLocation`` attribute pointing to the XSD file for a certain XML +namespace. This location always starts with the XML namespace. This XML +namespace is then replaced with the XSD validation base path returned from +:method:`Extension::getXsdValidationBasePath() ` +method. This namespace is then followed by the rest of the path from the base +path to the file itself. + +By convention, the XSD file lives in the ``Resources/config/schema``, but you +can place it anywhere you like. You should return this path as the base path:: + + // src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php + + // ... + class AcmeHelloExtension extends Extension + { + // ... + + public function getXsdValidationBasePath() + { + return __DIR__.'/../Resources/config/schema'; + } + } + +Assume the XSD file is called ``hello-1.0.xsd``, the schema location will be +``http://acme_company.com/schema/dic/hello/hello-1.0.xsd``: + +.. code-block:: xml + + + + + + + + + + + + + +.. _`FrameworkBundle Configuration`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +.. _`TwigBundle Configuration`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php +.. _`XML namespace`: http://en.wikipedia.org/wiki/XML_namespace +.. _`XML schema`: http://en.wikipedia.org/wiki/XML_schema diff --git a/cookbook/bundles/extension.rst b/cookbook/bundles/extension.rst index d35580aabca..cc4ce322399 100644 --- a/cookbook/bundles/extension.rst +++ b/cookbook/bundles/extension.rst @@ -2,113 +2,36 @@ single: Configuration; Semantic single: Bundle; Extension configuration -How to Expose a semantic Configuration for a Bundle -=================================================== +How to Load Service Configuration inside a Bundle +================================================= -If you open your application configuration file (usually ``app/config/config.yml``), -you'll see a number of different configuration "namespaces", such as ``framework``, -``twig``, and ``doctrine``. Each of these configures a specific bundle, allowing -you to configure things at a high level and then let the bundle make all the -low-level, complex changes that result. +In Symfony, you'll find yourself using many services. These services can be +registered in the `app/config` directory of your application. But when you +want to decouple the bundle for use in other projects, you want to include the +service configuration in the bundle itself. This article will teach you how to +do that. -For example, the following tells the FrameworkBundle to enable the form -integration, which involves the defining of quite a few services as well -as integration of other related components: - -.. configuration-block:: - - .. code-block:: yaml - - framework: - # ... - form: true - - .. code-block:: xml - - - - - - .. code-block:: php - - $container->loadFromExtension('framework', array( - // ... - 'form' => true, - // ... - )); - -When you create a bundle, you have two choices on how to handle configuration: - -1. **Normal Service Configuration** (*easy*): - - You can specify your services in a configuration file (e.g. ``services.yml``) - that lives in your bundle and then import it from your main application - configuration. This is really easy, quick and totally effective. If you - make use of :ref:`parameters `, then - you still have the flexibility to customize your bundle from your application - configuration. See ":ref:`service-container-imports-directive`" for more - details. - -2. **Exposing Semantic Configuration** (*advanced*): - - This is the way configuration is done with the core bundles (as described - above). The basic idea is that, instead of having the user override individual - parameters, you let the user configure just a few, specifically created - options. As the bundle developer, you then parse through that configuration - and load services inside an "Extension" class. With this method, you won't - need to import any configuration resources from your main application - configuration: the Extension class can handle all of this. - -The second option - which you'll learn about in this article - is much more -flexible, but also requires more time to setup. If you're wondering which -method you should use, it's probably a good idea to start with method #1, -and then change to #2 later if you need to. If you plan to distribute your -bundle, the second option is recommended. - -The second method has several specific advantages: - -* Much more powerful than simply defining parameters: a specific option value - might trigger the creation of many service definitions; - -* Ability to have configuration hierarchy; - -* Smart merging when several configuration files (e.g. ``config_dev.yml`` - and ``config.yml``) override each other's configuration; - -* Configuration validation (if you use a :ref:`Configuration Class `); - -* IDE auto-completion when you create an XSD and developers use XML. - -.. sidebar:: Overriding bundle parameters - - If a Bundle provides an Extension class, then you should generally *not* - override any service container parameters from that bundle. The idea - is that if an Extension class is present, every setting that should be - configurable should be present in the configuration made available by - that class. In other words the extension class defines all the publicly - supported configuration settings for which backward compatibility will - be maintained. - -.. seealso:: +Creating an Extension Class +--------------------------- - For parameter handling within a Dependency Injection class see - :doc:`/cookbook/configuration/using_parameters_in_dic`. +In order to load service configuration, you have to create a Dependency +Injection Extension for your bundle. This class has some conventions in order +to be detected automatically. But you'll later see how you can change it to +your own preferences. By default, the Extension has to comply with the +following conventions: -.. index:: - single: Bundle; Extension - single: DependencyInjection; Extension +* It has to live in the ``DependencyInjection`` namespace of the bundle; -Creating an Extension Class ---------------------------- +* The name is equal to the bundle name with the ``Bundle`` suffix replaced by + ``Extension`` (e.g. the Extension class of ``AcmeHelloBundle`` would be + called ``AcmeHelloExtension``). -If you do choose to expose a semantic configuration for your bundle, you'll -first need to create a new "Extension" class, which will handle the process. -This class should live in the ``DependencyInjection`` directory of your bundle -and its name should be constructed by replacing the ``Bundle`` suffix of the -Bundle class name with ``Extension``. For example, the Extension class of -``AcmeHelloBundle`` would be called ``AcmeHelloExtension``:: +The Extension class should implement the +:class:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface`, +but usually you would simply extend the +:class:`Symfony\\Component\\DependencyInjection\\Extension\\Extension` class:: - // Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php + // src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php namespace Acme\HelloBundle\DependencyInjection; use Symfony\Component\HttpKernel\DependencyInjection\Extension; @@ -118,496 +41,80 @@ Bundle class name with ``Extension``. For example, the Extension class of { public function load(array $configs, ContainerBuilder $container) { - // ... where all of the heavy logic is done - } - - public function getXsdValidationBasePath() - { - return __DIR__.'/../Resources/config/'; - } - - public function getNamespace() - { - return 'http://www.example.com/symfony/schema/'; + // ... you'll load the files here later } } -.. note:: - - The ``getXsdValidationBasePath`` and ``getNamespace`` methods are only - required if the bundle provides optional XSD's for the configuration. - -The presence of the previous class means that you can now define an ``acme_hello`` -configuration namespace in any configuration file. The namespace ``acme_hello`` -is constructed from the extension's class name by removing the word ``Extension`` -and then lowercasing and underscoring the rest of the name. In other words, -``AcmeHelloExtension`` becomes ``acme_hello``. - -You can begin specifying configuration under this namespace immediately: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - acme_hello: ~ - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('acme_hello', array()); - -.. tip:: - - If you follow the naming conventions laid out above, then the ``load()`` - method of your extension code is always called as long as your bundle - is registered in the Kernel. In other words, even if the user does not - provide any configuration (i.e. the ``acme_hello`` entry doesn't even - appear), the ``load()`` method will be called and passed an empty ``$configs`` - array. You can still provide some sensible defaults for your bundle if - you want. - -Registering the Extension Class -------------------------------- - -An Extension class will automatically be registered by Symfony2 when -following these simple conventions: - -* The extension must be stored in the ``DependencyInjection`` sub-namespace; - -* The extension must be named after the bundle name and suffixed with - ``Extension`` (``AcmeHelloExtension`` for ``AcmeHelloBundle``); - -* The extension *should* provide an XSD schema (but will be registered automatically - regardless). - Manually Registering an Extension Class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When not following the conventions, you will have to manually register your -extension. To manually register an extension class override the -:method:`Bundle::build() ` -method in your bundle:: +extension. To do this, you should override the +:method:`Bundle::getContainerExtension() ` +method to return the instance of the extension:: // ... use Acme\HelloBundle\DependencyInjection\UnconventionalExtensionClass; class AcmeHelloBundle extends Bundle { - public function build(ContainerBuilder $container) + public function getContainerExtension() { - parent::build($container); - - // register extensions that do not follow the conventions manually - $container->registerExtension(new UnconventionalExtensionClass()); - } - } - -In this case, the extension class must also implement a ``getAlias()`` method -and return a unique alias named after the bundle (e.g. ``acme_hello``). This -is required because the class name doesn't follow the conventions by ending -in ``Extension``. - -Additionally, the ``load()`` method of your extension will *only* be called -if the user specifies the ``acme_hello`` alias in at least one configuration -file. Once again, this is because the Extension class doesn't follow the -conventions set out above, so nothing happens automatically. - -Parsing the ``$configs`` Array ------------------------------- - -Whenever a user includes the ``acme_hello`` namespace in a configuration file, -the configuration under it is added to an array of configurations and -passed to the ``load()`` method of your extension (Symfony2 automatically -converts XML and YAML to an array). - -Take the following configuration: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - acme_hello: - foo: fooValue - bar: barValue - - .. code-block:: xml - - - - - - - - barValue - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('acme_hello', array( - 'foo' => 'fooValue', - 'bar' => 'barValue', - )); - -The array passed to your ``load()`` method will look like this:: - - array( - array( - 'foo' => 'fooValue', - 'bar' => 'barValue', - ), - ) - -Notice that this is an *array of arrays*, not just a single flat array of the -configuration values. This is intentional. For example, if ``acme_hello`` -appears in another configuration file - say ``config_dev.yml`` - with different -values beneath it, then the incoming array might look like this:: - - array( - array( - 'foo' => 'fooValue', - 'bar' => 'barValue', - ), - array( - 'foo' => 'fooDevValue', - 'baz' => 'newConfigEntry', - ), - ) - -The order of the two arrays depends on which one is set first. - -It's your job, then, to decide how these configurations should be merged -together. You might, for example, have later values override previous values -or somehow merge them together. - -Later, in the :ref:`Configuration Class ` -section, you'll learn of a truly robust way to handle this. But for now, -you might just merge them manually:: - - public function load(array $configs, ContainerBuilder $container) - { - $config = array(); - foreach ($configs as $subConfig) { - $config = array_merge($config, $subConfig); + return new UnconventionalExtensionClass(); } - - // ... now use the flat $config array } -.. caution:: - - Make sure the above merging technique makes sense for your bundle. This - is just an example, and you should be careful to not use it blindly. +Since the new Extension class name doesn't follow the naming conventions, you +should also override +:method:`Extension::getAlias() ` +to return the correct DI alias. The DI alias is the name used to refer to the +bundle in the container (e.g. in the ``app/config/config.yml`` file). By +default, this is done by removing the ``Extension`` prefix and converting the +class name to underscores (e.g. ``AcmeHelloExtension``'s DI alias is +``acme_hello``). Using the ``load()`` Method --------------------------- -Within ``load()``, the ``$container`` variable refers to a container that only -knows about this namespace configuration (i.e. it doesn't contain service -information loaded from other bundles). The goal of the ``load()`` method -is to manipulate the container, adding and configuring any methods or services -needed by your bundle. +In the ``load()`` method, all services and parameters related to this extension +will be loaded. This method doesn't get the actual container instance, but a +copy. This container only has the parameters from the actual container. After +loading the services and parameters, the copy will be merged into the actual +container, to ensure all services and parameters are also added to the actual +container. -Loading external Configuration Resources -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In the ``load()`` method, you can use PHP code to register service definitions, +but it is more common if you put the these definitions in a configuration file +(using the Yaml, XML or PHP format). Luckily, you can use the file loaders in +the extension! -One common thing to do is to load an external configuration file that may -contain the bulk of the services needed by your bundle. For example, suppose -you have a ``services.xml`` file that holds much of your bundle's service -configuration:: +For instance, assume you have a file called ``services.xml`` in the +``Resources/config`` directory of your bundle, your load method looks like:: use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\Config\FileLocator; + // ... public function load(array $configs, ContainerBuilder $container) { - // ... prepare your $config variable - - $loader = new XmlFileLoader( - $container, - new FileLocator(__DIR__.'/../Resources/config') - ); - $loader->load('services.xml'); - } - -You might even do this conditionally, based on one of the configuration values. -For example, suppose you only want to load a set of services if an ``enabled`` -option is passed and set to true:: - - public function load(array $configs, ContainerBuilder $container) - { - // ... prepare your $config variable - - $loader = new XmlFileLoader( - $container, - new FileLocator(__DIR__.'/../Resources/config') - ); - - if (isset($config['enabled']) && $config['enabled']) { - $loader->load('services.xml'); - } - } - -Configuring Services and Setting Parameters -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Once you've loaded some service configuration, you may need to modify the -configuration based on some of the input values. For example, suppose you -have a service whose first argument is some string "type" that it will use -internally. You'd like this to be easily configured by the bundle user, so -in your service configuration file (e.g. ``services.xml``), you define this -service and use a blank parameter - ``acme_hello.my_service_type`` - as -its first argument: - -.. code-block:: xml - - - - - - - - - - - %acme_hello.my_service_type% - - - - -But why would you define an empty parameter and then pass it to your service? -The answer is that you'll set this parameter in your extension class, based -on the incoming configuration values. Suppose, for example, that you want -to allow the user to define this *type* option under a key called ``my_type``. -Add the following to the ``load()`` method to do this:: - - public function load(array $configs, ContainerBuilder $container) - { - // ... prepare your $config variable - $loader = new XmlFileLoader( $container, new FileLocator(__DIR__.'/../Resources/config') ); $loader->load('services.xml'); - - if (!isset($config['my_type'])) { - throw new \InvalidArgumentException( - 'The "my_type" option must be set' - ); - } - - $container->setParameter( - 'acme_hello.my_service_type', - $config['my_type'] - ); } -Now, the user can effectively configure the service by specifying the ``my_type`` -configuration value: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - acme_hello: - my_type: foo - # ... - - .. code-block:: xml - - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('acme_hello', array( - 'my_type' => 'foo', - ..., - )); - -Global Parameters -~~~~~~~~~~~~~~~~~ - -When you're configuring the container, be aware that you have the following -global parameters available to use: - -* ``kernel.name`` -* ``kernel.environment`` -* ``kernel.debug`` -* ``kernel.root_dir`` -* ``kernel.cache_dir`` -* ``kernel.logs_dir`` -* ``kernel.bundles`` -* ``kernel.charset`` - -.. caution:: - - All parameter and service names starting with a ``_`` are reserved for the - framework, and new ones must not be defined by bundles. - -.. _cookbook-bundles-extension-config-class: - -Validation and Merging with a Configuration Class -------------------------------------------------- - -So far, you've done the merging of your configuration arrays by hand and -are checking for the presence of config values manually using the ``isset()`` -PHP function. An optional *Configuration* system is also available which -can help with merging, validation, default values, and format normalization. +Other available loaders are the ``YamlFileLoader``, ``PhpFileLoader`` and +``IniFileLoader``. .. note:: - Format normalization refers to the fact that certain formats - largely XML - - result in slightly different configuration arrays and that these arrays - need to be "normalized" to match everything else. - -To take advantage of this system, you'll create a ``Configuration`` class -and build a tree that defines your configuration in that class:: - - // src/Acme/HelloBundle/DependencyInjection/Configuration.php - namespace Acme\HelloBundle\DependencyInjection; - - use Symfony\Component\Config\Definition\Builder\TreeBuilder; - use Symfony\Component\Config\Definition\ConfigurationInterface; - - class Configuration implements ConfigurationInterface - { - public function getConfigTreeBuilder() - { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('acme_hello'); - - $rootNode - ->children() - ->scalarNode('my_type')->defaultValue('bar')->end() - ->end(); - - return $treeBuilder; - } - } - -This is a *very* simple example, but you can now use this class in your ``load()`` -method to merge your configuration and force validation. If any options other -than ``my_type`` are passed, the user will be notified with an exception -that an unsupported option was passed:: - - public function load(array $configs, ContainerBuilder $container) - { - $configuration = new Configuration(); - - $config = $this->processConfiguration($configuration, $configs); - - // ... - } - -The ``processConfiguration()`` method uses the configuration tree you've defined -in the ``Configuration`` class to validate, normalize and merge all of the -configuration arrays together. - -The ``Configuration`` class can be much more complicated than shown here, -supporting array nodes, "prototype" nodes, advanced validation, XML-specific -normalization and advanced merging. You can read more about this in -:doc:`the Config component documentation `. -You can also see it in action by checking out some of the core Configuration classes, -such as the one from the `FrameworkBundle Configuration`_ or the `TwigBundle Configuration`_. - -Modifying the Configuration of another Bundle -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you have multiple bundles that depend on each other, it may be useful -to allow one ``Extension`` class to modify the configuration passed to another -bundle's ``Extension`` class, as if the end-developer has actually placed that -configuration in their ``app/config/config.yml`` file. - -For more details, see :doc:`/cookbook/bundles/prepend_extension`. - -Default Configuration Dump -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``config:dump-reference`` command allows a bundle's default configuration to -be output to the console in YAML. - -As long as your bundle's configuration is located in the standard location -(``YourBundle\DependencyInjection\Configuration``) and does not have a -``__construct()`` it will work automatically. If you have something -different, your ``Extension`` class must override the -:method:`Extension::getConfiguration() ` -method and return an instance of your -``Configuration``. - -Comments and examples can be added to your configuration nodes using the -``->info()`` and ``->example()`` methods:: - - // src/Acme/HelloBundle/DependencyExtension/Configuration.php - namespace Acme\HelloBundle\DependencyInjection; - - use Symfony\Component\Config\Definition\Builder\TreeBuilder; - use Symfony\Component\Config\Definition\ConfigurationInterface; - - class Configuration implements ConfigurationInterface - { - public function getConfigTreeBuilder() - { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('acme_hello'); - - $rootNode - ->children() - ->scalarNode('my_type') - ->defaultValue('bar') - ->info('what my_type configures') - ->example('example setting') - ->end() - ->end() - ; + The ``IniFileLoader`` can only be used to load parameters and it can only + load them as strings. - return $treeBuilder; - } - } - -This text appears as YAML comments in the output of the ``config:dump-reference`` -command. - -.. index:: - pair: Convention; Configuration +Using Configuration to Change the Services +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. _`FrameworkBundle Configuration`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php -.. _`TwigBundle Configuration`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php +The Extension is also the class that handles the configuration for that +particular bundle (e.g. the configuration in ``app/config/config.yml``). To +read more about it, see the ":doc:`/cookbook/bundles/configuration`" article. diff --git a/cookbook/bundles/index.rst b/cookbook/bundles/index.rst index df0cf217b9d..87641de5e23 100644 --- a/cookbook/bundles/index.rst +++ b/cookbook/bundles/index.rst @@ -10,4 +10,5 @@ Bundles override remove extension + configuration prepend_extension diff --git a/cookbook/map.rst.inc b/cookbook/map.rst.inc index 20992e334b9..8f6486c0115 100644 --- a/cookbook/map.rst.inc +++ b/cookbook/map.rst.inc @@ -14,6 +14,7 @@ * :doc:`/cookbook/bundles/override` * :doc:`/cookbook/bundles/remove` * :doc:`/cookbook/bundles/extension` + * :doc:`/cookbook/bundles/configuration` * :doc:`/cookbook/bundles/prepend_extension` * :doc:`/cookbook/cache/index`