From 426e289ec2fb34315c231acea98ea2450ab7bd79 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Wed, 11 May 2022 22:48:26 -0400 Subject: [PATCH 1/9] Documenting Abstract Bundle and Extension --- bundles.rst | 53 +++++++++----- bundles/configuration.rst | 51 ++++++++++++++ bundles/extension.rst | 41 +++++++++++ bundles/override.rst | 18 ++--- bundles/prepend_extension.rst | 41 +++++++++++ configuration/micro_kernel_trait.rst | 101 ++++++++++++++++++++++----- 6 files changed, 261 insertions(+), 44 deletions(-) diff --git a/bundles.rst b/bundles.rst index ed194614c34..2c4b20fa694 100644 --- a/bundles.rst +++ b/bundles.rst @@ -48,18 +48,24 @@ The new bundle is called AcmeTestBundle, where the ``Acme`` portion is an exampl name that should be replaced by some "vendor" name that represents you or your organization (e.g. ABCTestBundle for some company named ``ABC``). -Start by creating a ``src/Acme/TestBundle/`` directory and adding a new file +Start by creating a ``Acme/TestBundle/src/`` directory and adding a new file called ``AcmeTestBundle.php``:: - // src/Acme/TestBundle/AcmeTestBundle.php - namespace App\Acme\TestBundle; + // Acme/TestBundle/src/AcmeTestBundle.php + namespace Acme\TestBundle; - use Symfony\Component\HttpKernel\Bundle\Bundle; + use Symfony\Component\HttpKernel\Bundle\AbstractBundle; - class AcmeTestBundle extends Bundle + class AcmeTestBundle extends AbstractBundle { } +.. versionadded:: 6.1 + + The ``AbstractBundle`` was introduced in Symfony 6.1. If your bundle must be compatible + with previous Symfony versions you have to extend from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle` + instead. + .. tip:: The name AcmeTestBundle follows the standard @@ -67,14 +73,27 @@ called ``AcmeTestBundle.php``:: also choose to shorten the name of the bundle to simply TestBundle by naming this class TestBundle (and naming the file ``TestBundle.php``). -This empty class is the only piece you need to create the new bundle. Though +It's recommended to place your bundle class in the ``src/`` directory and keep out all the +configuration files, templates, translations, etc. By default, Symfony determines the bundle path from the +directory where the bundle class is placed, so you have to define the :method:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle::getPath` +method to tell Symfony what is the root directory of your bundle path:: + + class AcmeTestBundle extends AbstractBundle + { + public function getPath(): string + { + return \dirname(__DIR__); + } + } + +This almost empty class is the only piece you need to create the new bundle. Though commonly empty, this class is powerful and can be used to customize the behavior of the bundle. Now that you've created the bundle, enable it:: // config/bundles.php return [ // ... - App\Acme\TestBundle\AcmeTestBundle::class => ['all' => true], + Acme\TestBundle\AcmeTestBundle::class => ['all' => true], ]; And while it doesn't do anything yet, AcmeTestBundle is now ready to be used. @@ -86,26 +105,24 @@ The directory structure of a bundle is meant to help to keep code consistent between all Symfony bundles. It follows a set of conventions, but is flexible to be adjusted if needed: -``Controller/`` +``src/Controller/`` Contains the controllers of the bundle (e.g. ``RandomController.php``). -``DependencyInjection/`` - Holds certain Dependency Injection Extension classes, which may import service - configuration, register compiler passes or more (this directory is not - necessary). - -``Resources/config/`` +``config/`` Houses configuration, including routing configuration (e.g. ``routing.yaml``). -``Resources/views/`` - Holds templates organized by controller name (e.g. ``Random/index.html.twig``). +``templates/`` + Holds templates organized by controller name (e.g. ``random/index.html.twig``). + +``translations/`` + Holds translations organized by domain and locale (e.g. ``AcmeTestBundle.en.xlf``). -``Resources/public/`` +``public/`` Contains web assets (images, stylesheets, etc) and is copied or symbolically linked into the project ``public/`` directory via the ``assets:install`` console command. -``Tests/`` +``tests/`` Holds all tests for the bundle. A bundle can be as small or large as the feature it implements. It contains diff --git a/bundles/configuration.rst b/bundles/configuration.rst index 1742457fb36..3df59c8b032 100644 --- a/bundles/configuration.rst +++ b/bundles/configuration.rst @@ -431,6 +431,57 @@ Assuming the XSD file is called ``hello-1.0.xsd``, the schema location will be +Defining Configuration directly in your Bundle class +---------------------------------------------------- + +.. versionadded:: 6.1 + + The ``AbstractBundle`` class is introduced in Symfony 6.1. + +As another option, you can define the extension configuration directly in your Bundle +class by implementing :class:`Symfony\\Component\\Config\\Definition\\ConfigurableInterface`, +which is already supported when your bundle extend from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle`:: + + use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; + use Symfony\Component\HttpKernel\Bundle\AbstractBundle; + + class AcmeFooBundle extends AbstractBundle + { + public function configure(DefinitionConfigurator $definition): void + { + // loads config definition from a file + $definition->import('../config/definition.php'); + + // loads config definition from multiple files (when it's too long you can split it) + $definition->import('../config/definition/*.php'); + + // if the configuration is short, consider adding it in this class + $definition->rootNode() + ->children() + ->scalarNode('foo')->defaultValue('bar')->end() + ->end() + ; + } + } + +This method is a shortcut of the previous "Extension", "Configuration" and "TreeBuilder" convention, +where you also have the possibility to import configuration definition from an external file:: + + // Acme/FooBundle/config/definition.php + use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; + + return static function (DefinitionConfigurator $definition) { + $definition->rootNode() + ->children() + ->scalarNode('foo')->defaultValue('bar')->end() + ->end() + ; + }; + +.. note:: + + The "configure()" method is called only at compiler time. + .. _`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`: https://en.wikipedia.org/wiki/XML_namespace diff --git a/bundles/extension.rst b/bundles/extension.rst index bbbfd398018..b8bccea6ca8 100644 --- a/bundles/extension.rst +++ b/bundles/extension.rst @@ -147,3 +147,44 @@ the full classmap executing the ``dump-autoload`` command of Composer. This technique can't be used when the classes to compile use the ``__DIR__`` or ``__FILE__`` constants, because their values will change when loading these classes from the ``classes.php`` file. + +Loading Services directly in your Bundle class +---------------------------------------------- + +.. versionadded:: 6.1 + + The ``AbstractBundle`` class is introduced in Symfony 6.1. + +Alternatively, you can define and load services configuration directly in a bundle class +by extending from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` +and defining the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::loadExtension` method:: + + use Symfony\Component\HttpKernel\Bundle\AbstractBundle; + use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + + class AcmeFooBundle extends AbstractBundle + { + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + $container->parameters() + ->set('foo', $config['foo']); + + $container->import('../config/services.php'); + + if ('bar' === $config['foo']) { + $container->services() + ->set(Parser::class); + } + } + } + +This method is a shortcut of the previous "load()" method, but with more options +to define and import the service configuration with less effort. The ``$config`` +argument is the previous ``$configs`` array but already merged and processed. And +through the ``$container`` configurator you can import the services configuration +from an external file in any supported format (php, yaml, xml) or simply define +them in place using the fluent interfaces. + +.. note:: + + The "loadExtension()" like "load()" method is called only at compiler time. diff --git a/bundles/override.rst b/bundles/override.rst index bf53eb5ce3c..71bbe40ac7f 100644 --- a/bundles/override.rst +++ b/bundles/override.rst @@ -12,7 +12,7 @@ features of a bundle. The bundle overriding mechanism means that you cannot use physical paths to refer to bundle's resources (e.g. ``__DIR__/config/services.xml``). Always - use logical paths in your bundles (e.g. ``@FooBundle/Resources/config/services.xml``) + use logical paths in your bundles (e.g. ``@FooBundle/config/services.xml``) and call the :ref:`locateResource() method ` to turn them into physical paths when needed. @@ -23,12 +23,12 @@ Templates Third-party bundle templates can be overridden in the ``/templates/bundles//`` directory. The new templates -must use the same name and path (relative to ``/Resources/views/``) as +must use the same name and path (relative to ``/templates/``) as the original templates. -For example, to override the ``Resources/views/Registration/confirmed.html.twig`` -template from the FOSUserBundle, create this template: -``/templates/bundles/FOSUserBundle/Registration/confirmed.html.twig`` +For example, to override the ``templates/registration/confirmed.html.twig`` +template from the AcmeUserBundle, create this template: +``/templates/bundles/AcmeUserBundle/registration/confirmed.html.twig`` .. caution:: @@ -43,9 +43,9 @@ extend from the original template, not from the overridden one: .. code-block:: twig - {# templates/bundles/FOSUserBundle/Registration/confirmed.html.twig #} + {# templates/bundles/AcmeUserBundle/registration/confirmed.html.twig #} {# the special '!' prefix avoids errors when extending from an overridden template #} - {% extends "@!FOSUser/Registration/confirmed.html.twig" %} + {% extends "@!AcmeUser/registration/confirmed.html.twig" %} {% block some_block %} ... @@ -173,7 +173,7 @@ For this reason, you can override any bundle translation file from the main ``translations/`` directory, as long as the new file uses the same domain. For example, to override the translations defined in the -``Resources/translations/FOSUserBundle.es.yml`` file of the FOSUserBundle, -create a ``/translations/FOSUserBundle.es.yml`` file. +``translations/AcmeUserBundle.es.yaml`` file of the AcmeUserBundle, +create a ``/translations/AcmeUserBundle.es.yaml`` file. .. _`the Doctrine documentation`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/inheritance-mapping.html#overrides diff --git a/bundles/prepend_extension.rst b/bundles/prepend_extension.rst index c23f9133ff4..848ef700267 100644 --- a/bundles/prepend_extension.rst +++ b/bundles/prepend_extension.rst @@ -157,3 +157,44 @@ More than one Bundle using PrependExtensionInterface If there is more than one bundle that prepends the same extension and defines the same key, the bundle that is registered **first** will take priority: next bundles won't override this specific config setting. + +Prepending Extension directly in your Bundle class +-------------------------------------------------- + +.. versionadded:: 6.1 + + The ``AbstractBundle`` class is introduced in Symfony 6.1. + +By preference, you can append or prepend extension configuration directly in your Bundle +class for any bundle by extending from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` +and defining the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::prependExtension` method:: + + use Symfony\Component\HttpKernel\Bundle\AbstractBundle; + use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + + class FooBundle extends AbstractBundle + { + public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void + { + // prepend + $builder->prependExtensionConfig('framework', [ + 'cache' => ['prefix_seed' => 'foo/bar'], + ]); + + // append + $container->extension('framework', [ + 'cache' => ['prefix_seed' => 'foo/bar'], + ]) + + // append from file + $container->import('../config/packages/cache.php'); + } + } + +This method is a shortcut of the previous "PrependExtensionInterface::prepend" method, +allowing you also to import and append extension config from an external file in one of +the supported formats (php, yaml, xml). + +.. note:: + + The "prependExtension()" like "prepend()" method is called only at compiler time. diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst index 6a2c6a6375a..7466cfb0b22 100644 --- a/configuration/micro_kernel_trait.rst +++ b/configuration/micro_kernel_trait.rst @@ -99,8 +99,8 @@ that define your bundles, your services and your routes: ``RoutingConfigurator`` has methods that make adding routes in PHP more fun. You can also load external routing files (shown below). -Advanced Example: Twig, Annotations and the Web Debug Toolbar -------------------------------------------------------------- +Advanced Example: Configuration, Twig, Annotations and the Web Debug Toolbar +---------------------------------------------------------------------------- The purpose of the ``MicroKernelTrait`` is *not* to have a single-file application. Instead, its goal to give you the power to choose your bundles and structure. @@ -123,13 +123,15 @@ your ``composer.json`` file to load from there: Then, run ``composer dump-autoload`` to dump your new autoload config. -Now, suppose you want to use Twig and load routes via annotations. Instead of -putting *everything* in ``index.php``, create a new ``src/Kernel.php`` to -hold the kernel. Now it looks like this:: +Now, suppose you want to define a custom configuration for your app, +use Twig and load routes via annotations. Instead of putting *everything* +in ``index.php``, create a new ``src/Kernel.php`` to hold the kernel. +Now it looks like this:: // src/Kernel.php namespace App; + use App\DependencyInjection\AppExtension; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\HttpKernel\Kernel as BaseKernel; @@ -146,16 +148,22 @@ hold the kernel. Now it looks like this:: new \Symfony\Bundle\TwigBundle\TwigBundle(), ]; - if ($this->getEnvironment() == 'dev') { + if ('dev' === $this->getEnvironment()) { $bundles[] = new \Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); } return $bundles; } + protected function build(ContainerBuilder $container) + { + $container->registerExtension(new AppExtension()); + } + protected function configureContainer(ContainerConfigurator $c): void { $c->import(__DIR__.'/../config/framework.yaml'); + $c->import(__DIR__.'/../config/web_profiler.yaml'); // register all classes in /src/ as service $c->services() @@ -163,14 +171,6 @@ hold the kernel. Now it looks like this:: ->autowire() ->autoconfigure() ; - - // configure WebProfilerBundle only if the bundle is enabled - if (isset($this->bundles['WebProfilerBundle'])) { - $c->extension('web_profiler', [ - 'toolbar' => true, - 'intercept_redirects' => false, - ]); - } } protected function configureRoutes(RoutingConfigurator $routes): void @@ -205,6 +205,35 @@ Before continuing, run this command to add support for the new dependencies: $ composer require symfony/yaml symfony/twig-bundle symfony/web-profiler-bundle doctrine/annotations +Next, create a new extension class that defines your app configuration and +add a service conditionally based on the ``foo`` value:: + + // src/DependencyInjection/AppExtension.php + namespace App\DependencyInjection; + + use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Component\DependencyInjection\Extension\AbstractExtension; + use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + + class AppExtension extends AbstractExtension + { + public function configure(DefinitionConfigurator $definition): void + { + $definition->rootNode() + ->children() + ->booleanNode('foo')->defaultTrue()->end() + ->end(); + } + + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + if ($config['foo']) { + $container->set('foo_service', new stdClass()); + } + } + } + Unlike the previous kernel, this loads an external ``config/framework.yaml`` file, because the configuration started to get bigger: @@ -245,6 +274,46 @@ because the configuration started to get bigger: ; }; +As well as the ``config/web_profiler.yaml`` file: + +.. configuration-block:: + + .. code-block:: yaml + + # config/web_profiler.yaml + when@dev: + web_profiler: + toolbar: true + intercept_redirects: false + + .. code-block:: xml + + + + + + + + + + + + .. code-block:: php + + // config/web_profiler.php + use Symfony\Config\WebProfilerConfig; + + return static function (WebProfilerConfig $webProfiler) { + $webProfiler + ->toolbar(true) + ->interceptRedirects(false) + ; + }; + This also loads annotation routes from an ``src/Controller/`` directory, which has one file in it:: @@ -257,9 +326,7 @@ has one file in it:: class MicroController extends AbstractController { - /** - * @Route("/random/{limit}") - */ + #[Route('/random/{limit}')] public function randomNumber(int $limit): Response { $number = random_int(0, $limit); From c93db0b280b50279ef94c03ed6ac6734862760c6 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Sat, 14 May 2022 08:39:06 -0400 Subject: [PATCH 2/9] Jules' review --- bundles/configuration.rst | 4 +- bundles/extension.rst | 2 +- bundles/prepend_extension.rst | 2 +- configuration/micro_kernel_trait.rst | 55 +++++++--------------------- 4 files changed, 17 insertions(+), 46 deletions(-) diff --git a/bundles/configuration.rst b/bundles/configuration.rst index 3df59c8b032..eda734f2020 100644 --- a/bundles/configuration.rst +++ b/bundles/configuration.rst @@ -465,7 +465,7 @@ which is already supported when your bundle extend from the :class:`Symfony\\Com } This method is a shortcut of the previous "Extension", "Configuration" and "TreeBuilder" convention, -where you also have the possibility to import configuration definition from an external file:: +now you also have the possibility to import configuration definition from an external file:: // Acme/FooBundle/config/definition.php use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; @@ -480,7 +480,7 @@ where you also have the possibility to import configuration definition from an e .. note:: - The "configure()" method is called only at compiler time. + The "configure()" method is called only at compile time. .. _`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 diff --git a/bundles/extension.rst b/bundles/extension.rst index b8bccea6ca8..ca88860bbe0 100644 --- a/bundles/extension.rst +++ b/bundles/extension.rst @@ -187,4 +187,4 @@ them in place using the fluent interfaces. .. note:: - The "loadExtension()" like "load()" method is called only at compiler time. + The "loadExtension()", as the "load()" method, are called only at compile time. diff --git a/bundles/prepend_extension.rst b/bundles/prepend_extension.rst index 848ef700267..479e7d22b08 100644 --- a/bundles/prepend_extension.rst +++ b/bundles/prepend_extension.rst @@ -197,4 +197,4 @@ the supported formats (php, yaml, xml). .. note:: - The "prependExtension()" like "prepend()" method is called only at compiler time. + The "prependExtension()" like "prepend()" method is called only at compile time. diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst index 7466cfb0b22..edb1f7426df 100644 --- a/configuration/micro_kernel_trait.rst +++ b/configuration/micro_kernel_trait.rst @@ -163,7 +163,6 @@ Now it looks like this:: protected function configureContainer(ContainerConfigurator $c): void { $c->import(__DIR__.'/../config/framework.yaml'); - $c->import(__DIR__.'/../config/web_profiler.yaml'); // register all classes in /src/ as service $c->services() @@ -171,6 +170,14 @@ Now it looks like this:: ->autowire() ->autoconfigure() ; + + // configure WebProfilerBundle only if the bundle is enabled + if (isset($this->bundles['WebProfilerBundle'])) { + $c->extension('web_profiler', [ + 'toolbar' => true, + 'intercept_redirects' => false, + ]); + } } protected function configureRoutes(RoutingConfigurator $routes): void @@ -229,11 +236,15 @@ add a service conditionally based on the ``foo`` value:: public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void { if ($config['foo']) { - $container->set('foo_service', new stdClass()); + $container->set('foo_service', new \stdClass()); } } } +.. versionadded:: 6.1 + + The ``AbstractExtension`` class is introduced in Symfony 6.1. + Unlike the previous kernel, this loads an external ``config/framework.yaml`` file, because the configuration started to get bigger: @@ -274,46 +285,6 @@ because the configuration started to get bigger: ; }; -As well as the ``config/web_profiler.yaml`` file: - -.. configuration-block:: - - .. code-block:: yaml - - # config/web_profiler.yaml - when@dev: - web_profiler: - toolbar: true - intercept_redirects: false - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // config/web_profiler.php - use Symfony\Config\WebProfilerConfig; - - return static function (WebProfilerConfig $webProfiler) { - $webProfiler - ->toolbar(true) - ->interceptRedirects(false) - ; - }; - This also loads annotation routes from an ``src/Controller/`` directory, which has one file in it:: From c2c47a21f63b2a5e387f7ca17e4920d8d164750e Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Sun, 15 May 2022 14:30:55 -0400 Subject: [PATCH 3/9] Update bundle.rst --- bundles.rst | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/bundles.rst b/bundles.rst index 2c4b20fa694..d1e526228e8 100644 --- a/bundles.rst +++ b/bundles.rst @@ -82,7 +82,7 @@ method to tell Symfony what is the root directory of your bundle path:: { public function getPath(): string { - return \dirname(__DIR__); + return \dirname(__DIR__); // returns /path/to/Acme/TestBundle/ } } @@ -105,8 +105,8 @@ The directory structure of a bundle is meant to help to keep code consistent between all Symfony bundles. It follows a set of conventions, but is flexible to be adjusted if needed: -``src/Controller/`` - Contains the controllers of the bundle (e.g. ``RandomController.php``). +``src/`` + Contains mainly PHP classes related to the bundle logic (e.g. ``Controller/RandomController.php``). ``config/`` Houses configuration, including routing configuration (e.g. ``routing.yaml``). @@ -125,6 +125,25 @@ to be adjusted if needed: ``tests/`` Holds all tests for the bundle. +It's recommended to use the `PSR-4`_ autoload standard: use the namespace as key, +and the location of the bundle's main class (relative to ``composer.json``) +as value. As the main class is located in the ``src/`` directory of the bundle: + +.. code-block:: json + + { + "autoload": { + "psr-4": { + "Acme\\TestBundle\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Acme\\TestBundle\\Tests\\": "tests/" + } + } + } + A bundle can be as small or large as the feature it implements. It contains only the files you need and nothing else. @@ -143,3 +162,4 @@ Learn more * :doc:`/bundles/prepend_extension` .. _`third-party bundles`: https://github.com/search?q=topic%3Asymfony-bundle&type=Repositories +.. _`PSR-4`: https://www.php-fig.org/psr/psr-4/ From d0cba728ea1aae3302192c3f487ca90f42e37d99 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Sat, 21 May 2022 10:15:27 -0400 Subject: [PATCH 4/9] Remove note after update the feature --- bundles.rst | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/bundles.rst b/bundles.rst index d1e526228e8..44731e47582 100644 --- a/bundles.rst +++ b/bundles.rst @@ -73,20 +73,7 @@ called ``AcmeTestBundle.php``:: also choose to shorten the name of the bundle to simply TestBundle by naming this class TestBundle (and naming the file ``TestBundle.php``). -It's recommended to place your bundle class in the ``src/`` directory and keep out all the -configuration files, templates, translations, etc. By default, Symfony determines the bundle path from the -directory where the bundle class is placed, so you have to define the :method:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle::getPath` -method to tell Symfony what is the root directory of your bundle path:: - - class AcmeTestBundle extends AbstractBundle - { - public function getPath(): string - { - return \dirname(__DIR__); // returns /path/to/Acme/TestBundle/ - } - } - -This almost empty class is the only piece you need to create the new bundle. Though +This empty class is the only piece you need to create the new bundle. Though commonly empty, this class is powerful and can be used to customize the behavior of the bundle. Now that you've created the bundle, enable it:: From 7cf9bf42a0f27f574cc723e0e178fea44531b589 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Sat, 21 May 2022 10:54:48 -0400 Subject: [PATCH 5/9] Add warning --- bundles.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bundles.rst b/bundles.rst index 44731e47582..2b0f224129b 100644 --- a/bundles.rst +++ b/bundles.rst @@ -62,9 +62,12 @@ called ``AcmeTestBundle.php``:: .. versionadded:: 6.1 - The ``AbstractBundle`` was introduced in Symfony 6.1. If your bundle must be compatible - with previous Symfony versions you have to extend from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle` - instead. + The ``AbstractBundle`` was introduced in Symfony 6.1. + +.. warning:: + + If your bundle must be compatible with previous Symfony versions you have to + extend from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle` instead. .. tip:: From 558b02eee1988a2cdb28f0236c43b7c3e9795a25 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 22 May 2022 14:29:34 +0200 Subject: [PATCH 6/9] Rewrite the bundle docs a bit more --- bundles.rst | 67 ++++++++------ bundles/configuration.rst | 133 +++++++++++++++++---------- bundles/extension.rst | 94 ++++++++++--------- bundles/prepend_extension.rst | 32 +++---- configuration/micro_kernel_trait.rst | 4 +- 5 files changed, 191 insertions(+), 139 deletions(-) diff --git a/bundles.rst b/bundles.rst index 2b0f224129b..81c21260032 100644 --- a/bundles.rst +++ b/bundles.rst @@ -46,12 +46,11 @@ Creating a Bundle This section creates and enables a new bundle to show there are only a few steps required. The new bundle is called AcmeTestBundle, where the ``Acme`` portion is an example name that should be replaced by some "vendor" name that represents you or your -organization (e.g. ABCTestBundle for some company named ``ABC``). +organization (e.g. AbcTestBundle for some company named ``Abc``). -Start by creating a ``Acme/TestBundle/src/`` directory and adding a new file -called ``AcmeTestBundle.php``:: +Start by adding creating a new class called ``AcmeTestBundle``:: - // Acme/TestBundle/src/AcmeTestBundle.php + // src/AcmeTestBundle.php namespace Acme\TestBundle; use Symfony\Component\HttpKernel\Bundle\AbstractBundle; @@ -62,9 +61,10 @@ called ``AcmeTestBundle.php``:: .. versionadded:: 6.1 - The ``AbstractBundle`` was introduced in Symfony 6.1. + The :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` was + introduced in Symfony 6.1. -.. warning:: +.. caution:: If your bundle must be compatible with previous Symfony versions you have to extend from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle` instead. @@ -96,7 +96,7 @@ between all Symfony bundles. It follows a set of conventions, but is flexible to be adjusted if needed: ``src/`` - Contains mainly PHP classes related to the bundle logic (e.g. ``Controller/RandomController.php``). + Contains all PHP classes related to the bundle logic (e.g. ``Controller/RandomController.php``). ``config/`` Houses configuration, including routing configuration (e.g. ``routing.yaml``). @@ -115,32 +115,44 @@ to be adjusted if needed: ``tests/`` Holds all tests for the bundle. -It's recommended to use the `PSR-4`_ autoload standard: use the namespace as key, -and the location of the bundle's main class (relative to ``composer.json``) -as value. As the main class is located in the ``src/`` directory of the bundle: +.. caution:: -.. code-block:: json + The recommended bundle structure was changed in Symfony 5, read the + `Symfony 4.4 bundle documentation`_ for information about the old + structure. - { - "autoload": { - "psr-4": { - "Acme\\TestBundle\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "Acme\\TestBundle\\Tests\\": "tests/" + When using the new ``AbstractBundle`` class, the bundle defaults to the + new structure. Override the ``Bundle::getPath()`` method to change to + the old structure:: + + class AcmeTestBundle extends AbstractBundle + { + public function getPath(): string + { + return __DIR__; } } - } -A bundle can be as small or large as the feature it implements. It contains -only the files you need and nothing else. +.. tip:: -As you move through the guides, you'll learn how to persist objects to a -database, create and validate forms, create translations for your application, -write tests and much more. Each of these has their own place and role within -the bundle. + It's recommended to use the `PSR-4`_ autoload standard: use the namespace as key, + and the location of the bundle's main class (relative to ``composer.json``) + as value. As the main class is located in the ``src/`` directory of the bundle: + + .. code-block:: json + + { + "autoload": { + "psr-4": { + "Acme\\TestBundle\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Acme\\TestBundle\\Tests\\": "tests/" + } + } + } Learn more ---------- @@ -152,4 +164,5 @@ Learn more * :doc:`/bundles/prepend_extension` .. _`third-party bundles`: https://github.com/search?q=topic%3Asymfony-bundle&type=Repositories +.. _`Symfony 4.4 bundle documentation`: https://symfony.com/doc/4.4/bundles.html#bundle-directory-structure .. _`PSR-4`: https://www.php-fig.org/psr/psr-4/ diff --git a/bundles/configuration.rst b/bundles/configuration.rst index eda734f2020..374171bd07f 100644 --- a/bundles/configuration.rst +++ b/bundles/configuration.rst @@ -315,6 +315,88 @@ In your extension, you can load this and dynamically set its arguments:: // ... now use the flat $config array } +Using the Bundle Class +---------------------- + +.. versionadded:: 6.1 + + The ``AbstractBundle`` class is introduced in Symfony 6.1. + +Instead of creating an extension and configuration class, 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\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 $container, ContainerBuilder $builder): void + { + // Contrary to the Extension class, the "$config" variable is already merged + // and processed. You can use it directly to configure the service container. + $container->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 definitino from one or more files:: + + // src/AcmeSocialBundle.php + + // ... + 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) { + $definition->rootNode() + ->children() + ->scalarNode('foo')->defaultValue('bar')->end() + ->end() + ; + }; + Modifying the Configuration of Another Bundle --------------------------------------------- @@ -431,57 +513,6 @@ Assuming the XSD file is called ``hello-1.0.xsd``, the schema location will be -Defining Configuration directly in your Bundle class ----------------------------------------------------- - -.. versionadded:: 6.1 - - The ``AbstractBundle`` class is introduced in Symfony 6.1. - -As another option, you can define the extension configuration directly in your Bundle -class by implementing :class:`Symfony\\Component\\Config\\Definition\\ConfigurableInterface`, -which is already supported when your bundle extend from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle`:: - - use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; - use Symfony\Component\HttpKernel\Bundle\AbstractBundle; - - class AcmeFooBundle extends AbstractBundle - { - public function configure(DefinitionConfigurator $definition): void - { - // loads config definition from a file - $definition->import('../config/definition.php'); - - // loads config definition from multiple files (when it's too long you can split it) - $definition->import('../config/definition/*.php'); - - // if the configuration is short, consider adding it in this class - $definition->rootNode() - ->children() - ->scalarNode('foo')->defaultValue('bar')->end() - ->end() - ; - } - } - -This method is a shortcut of the previous "Extension", "Configuration" and "TreeBuilder" convention, -now you also have the possibility to import configuration definition from an external file:: - - // Acme/FooBundle/config/definition.php - use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; - - return static function (DefinitionConfigurator $definition) { - $definition->rootNode() - ->children() - ->scalarNode('foo')->defaultValue('bar')->end() - ->end() - ; - }; - -.. note:: - - The "configure()" method is called only at compile time. - .. _`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`: https://en.wikipedia.org/wiki/XML_namespace diff --git a/bundles/extension.rst b/bundles/extension.rst index ca88860bbe0..b36bb8fb37e 100644 --- a/bundles/extension.rst +++ b/bundles/extension.rst @@ -97,7 +97,7 @@ For instance, assume you have a file called ``services.xml`` in the { $loader = new XmlFileLoader( $container, - new FileLocator(__DIR__.'/../Resources/config') + new FileLocator(__DIR__.'/../config') ); $loader->load('services.xml'); } @@ -111,6 +111,57 @@ 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 is 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\HttpKernel\Bundle\AbstractBundle; + use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; + + class AcmeHelloBundle extends AbstractBundle + { + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + // load an XML, PHP or Yaml file + $container->import('../config/services.xml'); + + // you can also add or replace parameters and services + $container->parameters() + ->set('acme_hello.phrase', $config['phrase']) + ; + + if ($config['scream']) { + $container->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 ------------------------- @@ -147,44 +198,3 @@ the full classmap executing the ``dump-autoload`` command of Composer. This technique can't be used when the classes to compile use the ``__DIR__`` or ``__FILE__`` constants, because their values will change when loading these classes from the ``classes.php`` file. - -Loading Services directly in your Bundle class ----------------------------------------------- - -.. versionadded:: 6.1 - - The ``AbstractBundle`` class is introduced in Symfony 6.1. - -Alternatively, you can define and load services configuration directly in a bundle class -by extending from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` -and defining the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::loadExtension` method:: - - use Symfony\Component\HttpKernel\Bundle\AbstractBundle; - use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; - - class AcmeFooBundle extends AbstractBundle - { - public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void - { - $container->parameters() - ->set('foo', $config['foo']); - - $container->import('../config/services.php'); - - if ('bar' === $config['foo']) { - $container->services() - ->set(Parser::class); - } - } - } - -This method is a shortcut of the previous "load()" method, but with more options -to define and import the service configuration with less effort. The ``$config`` -argument is the previous ``$configs`` array but already merged and processed. And -through the ``$container`` configurator you can import the services configuration -from an external file in any supported format (php, yaml, xml) or simply define -them in place using the fluent interfaces. - -.. note:: - - The "loadExtension()", as the "load()" method, are called only at compile time. diff --git a/bundles/prepend_extension.rst b/bundles/prepend_extension.rst index 479e7d22b08..7e9f0195d8c 100644 --- a/bundles/prepend_extension.rst +++ b/bundles/prepend_extension.rst @@ -151,25 +151,20 @@ registered and the ``entity_manager_name`` setting for ``acme_hello`` is set to 'use_acme_goodbye' => false, ]); -More than one Bundle using PrependExtensionInterface ----------------------------------------------------- - -If there is more than one bundle that prepends the same extension and defines -the same key, the bundle that is registered **first** will take priority: -next bundles won't override this specific config setting. - -Prepending Extension directly in your Bundle class --------------------------------------------------- +Prepending Extension in the Bundle Class +---------------------------------------- .. versionadded:: 6.1 The ``AbstractBundle`` class is introduced in Symfony 6.1. -By preference, you can append or prepend extension configuration directly in your Bundle -class for any bundle by extending from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` -and defining the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::prependExtension` method:: +You can also append or prepend extension configuration directly in your +Bundle class if you extend from the :class:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle` +class and define the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle::prependExtension` +method:: use Symfony\Component\HttpKernel\Bundle\AbstractBundle; + use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; class FooBundle extends AbstractBundle @@ -191,10 +186,13 @@ and defining the :method:`Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle } } -This method is a shortcut of the previous "PrependExtensionInterface::prepend" method, -allowing you also to import and append extension config from an external file in one of -the supported formats (php, yaml, xml). - .. note:: - The "prependExtension()" like "prepend()" method is called only at compile time. + The ``prependExtension()`` method, like ``prepend()``, is called only at compile time. + +More than one Bundle using PrependExtensionInterface +---------------------------------------------------- + +If there is more than one bundle that prepends the same extension and defines +the same key, the bundle that is registered **first** will take priority: +next bundles won't override this specific config setting. diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst index edb1f7426df..9cd2084858a 100644 --- a/configuration/micro_kernel_trait.rst +++ b/configuration/micro_kernel_trait.rst @@ -99,8 +99,8 @@ that define your bundles, your services and your routes: ``RoutingConfigurator`` has methods that make adding routes in PHP more fun. You can also load external routing files (shown below). -Advanced Example: Configuration, Twig, Annotations and the Web Debug Toolbar ----------------------------------------------------------------------------- +Advanced Example: Twig, Annotations and the Web Debug Toolbar +------------------------------------------------------------- The purpose of the ``MicroKernelTrait`` is *not* to have a single-file application. Instead, its goal to give you the power to choose your bundles and structure. From c85de085ed3021cf4cbf3786a3799b323e85406d Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Sun, 22 May 2022 12:22:19 -0400 Subject: [PATCH 7/9] Revert attribute route --- configuration/micro_kernel_trait.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/configuration/micro_kernel_trait.rst b/configuration/micro_kernel_trait.rst index 9cd2084858a..44b4c51b5d5 100644 --- a/configuration/micro_kernel_trait.rst +++ b/configuration/micro_kernel_trait.rst @@ -297,7 +297,9 @@ has one file in it:: class MicroController extends AbstractController { - #[Route('/random/{limit}')] + /** + * @Route("/random/{limit}") + */ public function randomNumber(int $limit): Response { $number = random_int(0, $limit); From c426dbbcd160d4ba9d4276fca87b94759614b2d4 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Sun, 22 May 2022 12:25:51 -0400 Subject: [PATCH 8/9] Fix extension/config paths --- bundles/extension.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bundles/extension.rst b/bundles/extension.rst index b36bb8fb37e..d05bf79a911 100644 --- a/bundles/extension.rst +++ b/bundles/extension.rst @@ -30,7 +30,7 @@ follow these conventions (but later you'll learn how to skip them if needed): This is how the extension of an AcmeHelloBundle should look like:: - // src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php + // src/DependencyInjection/AcmeHelloExtension.php namespace Acme\HelloBundle\DependencyInjection; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -87,7 +87,7 @@ but it is more common if you put these definitions in a configuration file (using the YAML, XML or PHP format). For instance, assume you have a file called ``services.xml`` in the -``Resources/config/`` directory of your bundle, your ``load()`` method looks like:: +``config/`` directory of your bundle, your ``load()`` method looks like:: use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; @@ -97,7 +97,7 @@ For instance, assume you have a file called ``services.xml`` in the { $loader = new XmlFileLoader( $container, - new FileLocator(__DIR__.'/../config') + new FileLocator(__DIR__.'/../../config') ); $loader->load('services.xml'); } From 7a24f08c9cab693b513e655e50ecbe85ac3ede46 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Sun, 22 May 2022 12:26:51 -0400 Subject: [PATCH 9/9] typo --- bundles/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/configuration.rst b/bundles/configuration.rst index 374171bd07f..2f3d7268b19 100644 --- a/bundles/configuration.rst +++ b/bundles/configuration.rst @@ -367,7 +367,7 @@ add this logic to the bundle class directly:: .. tip:: The ``AbstractBundle::configure()`` method also allows to import the - configuration definitino from one or more files:: + configuration definition from one or more files:: // src/AcmeSocialBundle.php