From e0faca500086ca9bad90f6bfe41c13164d284658 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 16 Apr 2024 17:52:36 +0200 Subject: [PATCH] Promote the new bundle structure everywhere --- bundles.rst | 4 + bundles/configuration.rst | 195 ++++++++++++++++++++------------------ bundles/extension.rst | 126 +++++++++++++----------- 3 files changed, 179 insertions(+), 146 deletions(-) diff --git a/bundles.rst b/bundles.rst index c937b1ac69f..bb055f5ea09 100644 --- a/bundles.rst +++ b/bundles.rst @@ -87,6 +87,8 @@ of the bundle. Now that you've created the bundle, enable it:: And while it doesn't do anything yet, AcmeBlogBundle is now ready to be used. +.. _bundles-directory-structure: + Bundle Directory Structure -------------------------- @@ -119,6 +121,8 @@ to be adjusted if needed: ``translations/`` Holds translations organized by domain and locale (e.g. ``AcmeBlogBundle.en.xlf``). +.. _bundles-legacy-directory-structure: + .. caution:: The recommended bundle structure was changed in Symfony 5, read the diff --git a/bundles/configuration.rst b/bundles/configuration.rst index c155fe8a56a..9aa7c96438c 100644 --- a/bundles/configuration.rst +++ b/bundles/configuration.rst @@ -46,11 +46,114 @@ as integration of other related components: $framework->form()->enabled(true); }; +There are two different ways of creating friendly configuration for a bundle: + +#. :ref:`Using the main bundle class `: + this is recommended for new bundles and for bundles following the + :ref:`recommended directory structure `; +#. :ref:`Using the Bundle extension class `: + this was the traditional way of doing it, but nowadays it's only recommended for + bundles following the :ref:`legacy directory structure `. + +.. _using-the-bundle-class: +.. _bundle-friendly-config-bundle-class: + +Using the AbstractBundle Class +------------------------------ + +.. versionadded:: 6.1 + + The ``AbstractBundle`` class was introduced in Symfony 6.1. + +In bundles extending the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` +class, you can add all the logic related to processing the configuration in that class:: + + // src/AcmeSocialBundle.php + namespace Acme\SocialBundle; + + use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + use Symfony\Component\HttpKernel\Bundle\AbstractBundle; + + class AcmeSocialBundle extends AbstractBundle + { + public function configure(DefinitionConfigurator $definition): void + { + $definition->rootNode() + ->children() + ->arrayNode('twitter') + ->children() + ->integerNode('client_id')->end() + ->scalarNode('client_secret')->end() + ->end() + ->end() // twitter + ->end() + ; + } + + public function loadExtension(array $config, ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void + { + // the "$config" variable is already merged and processed so you can + // use it directly to configure the service container (when defining an + // extension class, you also have to do this merging and processing) + $containerConfigurator->services() + ->get('acme.social.twitter_client') + ->arg(0, $config['twitter']['client_id']) + ->arg(1, $config['twitter']['client_secret']) + ; + } + } + +.. note:: + + The ``configure()`` and ``loadExtension()`` methods are called only at compile time. + +.. tip:: + + The ``AbstractBundle::configure()`` method also allows to import the + configuration definition from one or more files:: + + // src/AcmeSocialBundle.php + namespace Acme\SocialBundle; + + // ... + class AcmeSocialBundle extends AbstractBundle + { + public function configure(DefinitionConfigurator $definition): void + { + $definition->import('../config/definition.php'); + // you can also use glob patterns + //$definition->import('../config/definition/*.php'); + } + + // ... + } + + .. code-block:: php + + // config/definition.php + use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; + + return static function (DefinitionConfigurator $definition): void { + $definition->rootNode() + ->children() + ->scalarNode('foo')->defaultValue('bar')->end() + ->end() + ; + }; + +.. _bundle-friendly-config-extension: + Using the Bundle Extension -------------------------- +This is the traditional way of creating friendly configuration for bundles. For new +bundles it's recommended to :ref:`use the main bundle class `, +but this other way based on creating an extension class still works. + Imagine you are creating a new bundle - AcmeSocialBundle - which provides -integration with Twitter. To make your bundle configurable to the user, you +integration with X/Twitter. To make your bundle configurable to the user, you can add some configuration that looks like this: .. configuration-block:: @@ -110,7 +213,7 @@ load correct services and parameters inside an "Extension" class. 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 + 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. @@ -315,94 +418,6 @@ In your extension, you can load this and dynamically set its arguments:: // ... now use the flat $config array } -.. _using-the-bundle-class: - -Using the AbstractBundle Class ------------------------------- - -.. versionadded:: 6.1 - - The ``AbstractBundle`` class was introduced in Symfony 6.1. - -As an alternative, instead of creating an extension and configuration class as -shown in the previous section, you can also extend -:class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` to add this -logic to the bundle class directly:: - - // src/AcmeSocialBundle.php - namespace Acme\SocialBundle; - - use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; - use Symfony\Component\HttpKernel\Bundle\AbstractBundle; - - class AcmeSocialBundle extends AbstractBundle - { - public function configure(DefinitionConfigurator $definition): void - { - $definition->rootNode() - ->children() - ->arrayNode('twitter') - ->children() - ->integerNode('client_id')->end() - ->scalarNode('client_secret')->end() - ->end() - ->end() // twitter - ->end() - ; - } - - public function loadExtension(array $config, ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void - { - // Contrary to the Extension class, the "$config" variable is already merged - // and processed. You can use it directly to configure the service container. - $containerConfigurator->services() - ->get('acme.social.twitter_client') - ->arg(0, $config['twitter']['client_id']) - ->arg(1, $config['twitter']['client_secret']) - ; - } - } - -.. note:: - - The ``configure()`` and ``loadExtension()`` methods are called only at compile time. - -.. tip:: - - The ``AbstractBundle::configure()`` method also allows to import the - configuration definition from one or more files:: - - // src/AcmeSocialBundle.php - namespace Acme\SocialBundle; - - // ... - class AcmeSocialBundle extends AbstractBundle - { - public function configure(DefinitionConfigurator $definition): void - { - $definition->import('../config/definition.php'); - // you can also use glob patterns - //$definition->import('../config/definition/*.php'); - } - - // ... - } - - .. code-block:: php - - // config/definition.php - use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; - - return static function (DefinitionConfigurator $definition): void { - $definition->rootNode() - ->children() - ->scalarNode('foo')->defaultValue('bar')->end() - ->end() - ; - }; - Modifying the Configuration of Another Bundle --------------------------------------------- diff --git a/bundles/extension.rst b/bundles/extension.rst index 8b2928358ad..789deca6c09 100644 --- a/bundles/extension.rst +++ b/bundles/extension.rst @@ -6,12 +6,77 @@ file used by the application but in the bundles themselves. This article explains how to create and load service files using the bundle directory structure. +There are two different ways of doing it: + +#. :ref:`Load your services in the main bundle class `: + this is recommended for new bundles and for bundles following the + :ref:`recommended directory structure `; +#. :ref:`Create an extension class to load the service configuration files `: + this was the traditional way of doing it, but nowadays it's only recommended for + bundles following the :ref:`legacy directory structure `. + +.. _bundle-load-services-bundle-class: + +Loading Services Directly in your Bundle Class +---------------------------------------------- + +.. versionadded:: 6.1 + + The ``AbstractBundle`` class was introduced in Symfony 6.1. + +In bundles extending the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` +class, you can define the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::loadExtension` +method to load service definitions from configuration files:: + + // ... + use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + use Symfony\Component\HttpKernel\Bundle\AbstractBundle; + + class AcmeHelloBundle extends AbstractBundle + { + public function loadExtension(array $config, ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void + { + // load an XML, PHP or YAML file + $containerConfigurator->import('../config/services.xml'); + + // you can also add or replace parameters and services + $containerConfigurator->parameters() + ->set('acme_hello.phrase', $config['phrase']) + ; + + if ($config['scream']) { + $containerConfigurator->services() + ->get('acme_hello.printer') + ->class(ScreamingPrinter::class) + ; + } + } + } + +This method works similar to the ``Extension::load()`` method explained below, +but it uses a new simpler API to define and import service configuration. + +.. note:: + + Contrary to the ``$configs`` parameter in ``Extension::load()``, the + ``$config`` parameter is already merged and processed by the + ``AbstractBundle``. + +.. note:: + + The ``loadExtension()`` is called only at compile time. + +.. _bundle-load-services-extension: + Creating an Extension Class --------------------------- -In order to load service configuration, you have to create a Dependency -Injection (DI) Extension for your bundle. By default, the Extension class must -follow these conventions (but later you'll learn how to skip them if needed): +This is the traditional way of loading service definitions in bundles. For new +bundles it's recommended to :ref:`load your services in the main bundle class `, +but this other way based on creating an extension class still works. + +A depdendency injection extension is defined as a class that follows these +conventions (later you'll learn how to skip them if needed): * It has to live in the ``DependencyInjection`` namespace of the bundle; @@ -20,7 +85,7 @@ follow these conventions (but later you'll learn how to skip them if needed): :class:`Symfony\\Component\\DependencyInjection\\Extension\\Extension` class; * The name is equal to the bundle name with the ``Bundle`` suffix replaced by - ``Extension`` (e.g. the Extension class of the AcmeBundle would be called + ``Extension`` (e.g. the extension class of the AcmeBundle would be called ``AcmeExtension`` and the one for AcmeHelloBundle would be called ``AcmeHelloExtension``). @@ -70,7 +135,7 @@ class name to underscores (e.g. ``AcmeHelloExtension``'s DI alias is ``acme_hello``). Using the ``load()`` Method ---------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 @@ -108,57 +173,6 @@ The Extension is also the class that handles the configuration for that particular bundle (e.g. the configuration in ``config/packages/.yaml``). To read more about it, see the ":doc:`/bundles/configuration`" article. -Loading Services directly in your Bundle class ----------------------------------------------- - -.. versionadded:: 6.1 - - The ``AbstractBundle`` class was introduced in Symfony 6.1. - -Alternatively, you can define and load services configuration directly in a -bundle class instead of creating a specific ``Extension`` class. You can do -this by extending from :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` -and defining the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::loadExtension` -method:: - - // ... - use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; - use Symfony\Component\HttpKernel\Bundle\AbstractBundle; - - class AcmeHelloBundle extends AbstractBundle - { - public function loadExtension(array $config, ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void - { - // load an XML, PHP or Yaml file - $containerConfigurator->import('../config/services.xml'); - - // you can also add or replace parameters and services - $containerConfigurator->parameters() - ->set('acme_hello.phrase', $config['phrase']) - ; - - if ($config['scream']) { - $containerConfigurator->services() - ->get('acme_hello.printer') - ->class(ScreamingPrinter::class) - ; - } - } - } - -This method works similar to the ``Extension::load()`` method, but it uses -a new API to define and import service configuration. - -.. note:: - - Contrary to the ``$configs`` parameter in ``Extension::load()``, the - ``$config`` parameter is already merged and processed by the - ``AbstractBundle``. - -.. note:: - - The ``loadExtension()`` is called only at compile time. - Adding Classes to Compile -------------------------