Skip to content

Commit

Permalink
Promote the new bundle structure everywhere
Browse files Browse the repository at this point in the history
  • Loading branch information
javiereguiluz committed Apr 16, 2024
1 parent a5cbeaf commit e0faca5
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 146 deletions.
4 changes: 4 additions & 0 deletions bundles.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------------------

Expand Down Expand Up @@ -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
Expand Down
195 changes: 105 additions & 90 deletions bundles/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <bundle-friendly-config-bundle-class>`:
this is recommended for new bundles and for bundles following the
:ref:`recommended directory structure <bundles-directory-structure>`;
#. :ref:`Using the Bundle extension class <bundle-friendly-config-extension>`:
this was the traditional way of doing it, but nowadays it's only recommended for
bundles following the :ref:`legacy directory structure <bundles-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 <bundle-friendly-config-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::
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
---------------------------------------------

Expand Down
126 changes: 70 additions & 56 deletions bundles/extension.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <bundle-load-services-bundle-class>`:
this is recommended for new bundles and for bundles following the
:ref:`recommended directory structure <bundles-directory-structure>`;
#. :ref:`Create an extension class to load the service configuration files <bundle-load-services-extension>`:
this was the traditional way of doing it, but nowadays it's only recommended for
bundles following the :ref:`legacy directory structure <bundles-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 <bundle-load-services-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;

Expand All @@ -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``).

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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/<bundle_alias>.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
-------------------------

Expand Down

0 comments on commit e0faca5

Please sign in to comment.