Skip to content
This repository

[2.2] add possibility for bundles extensions to prepend the app configs #5566

Merged
merged 1 commit into from over 1 year ago

6 participants

Lukas Kahwe Smith Christophe Coevoet Jérôme Vieilledent Johnny Robeson Fabien Potencier Drak
Lukas Kahwe Smith

Bug fix: #4652
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
License of the code: MIT

As can be seen in the patch the extensions that should prepend the configuration are enabled automatically if they implement PrependExtensionInterface.

Just as an example, here an extension, which checks if SonataAdminBundle is available and if not disables integration with it in several Bundles. It also sets some default settings for document_class and default_document_manager_name:

diff --git a/DependencyInjection/SymfonyCmfCoreExtension.php b/DependencyInjection/SymfonyCmfCoreExtension.php
index 9f92410..c0a8dbb 100644
--- a/DependencyInjection/SymfonyCmfCoreExtension.php
+++ b/DependencyInjection/SymfonyCmfCoreExtension.php
@@ -3,11 +3,12 @@
 namespace Symfony\Cmf\Bundle\CoreBundle\DependencyInjection;

 use Symfony\Component\HttpKernel\DependencyInjection\Extension;
+use Symfony\Component\\DependencyInjection\PrependExtensionInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
 use Symfony\Component\Config\FileLocator;

-class SymfonyCmfCoreExtension extends Extension
+class SymfonyCmfCoreExtension extends Extension implements PrependExtensionInterface
 {
     public function load(array $configs, ContainerBuilder $container)
     {
@@ -15,4 +16,45 @@ class SymfonyCmfCoreExtension extends Extension
         $loader->load('config.xml');
         $loader->load('services.xml');
     }
+
+    public function prepend(ContainerBuilder $container)
+    {
+        $bundles = $container->getParameter('kernel.bundles');
+        if (!isset($bundles['SonataDoctrinePHPCRAdminBundle'])) {
+            // disable SonataDoctrinePHPCRAdminBundle admin support in Bundles
+            $config = array('use_sonata_admin' => false);
+            foreach ($container->getExtensions() as $name => $extension) {
+                switch ($name) {
+                    case 'symfony_cmf_menu':
+                    case 'symfony_cmf_routing_extra':
+                    case 'symfony_cmf_simple_cms':
+                        $container->prependExtensionConfig($name, $config);
+                        break;
+                }
+            }
+        }
+
+        // process the configuration of SymfonyCmfCoreExtension
+        $configs = $container->getExtensionConfig($this->getAlias());
+        $config = $this->processConfiguration(new Configuration(), $configs);
+        // add the default configs to various Bundles
+        foreach ($container->getExtensions() as $name => $extension) {
+            switch ($name) {
+                case 'symfony_cmf_content':
+                case 'symfony_cmf_simple_cms':
+                    $container->prependExtensionConfig($name, $config);
+                    break;
+                }
+        }
+    }
 }
src/Symfony/Component/HttpKernel/DependencyInjection/PreProcessExtensionInterface.php
((8 lines not shown))
  8
+ * For the full copyright and license information, please view the LICENSE
  9
+ * file that was distributed with this source code.
  10
+ */
  11
+
  12
+namespace Symfony\Component\HttpKernel\DependencyInjection;
  13
+
  14
+use Symfony\Component\DependencyInjection\ContainerBuilder;
  15
+
  16
+interface PreProcessExtensionInterface
  17
+{
  18
+    /**
  19
+     * Allow an extension to pre-process the extension configurations.
  20
+     *
  21
+     * @param ContainerBuilder $container
  22
+     */
  23
+    function preProcess(ContainerBuilder $container);
3
Christophe Coevoet Collaborator
stof added a note September 21, 2012

missing visibility

is this a new PSR-1/2 rule?

Christophe Coevoet Collaborator
stof added a note September 22, 2012

yes. It was one of the only points where the Symfony CS were incompatible with PSR-2 (symfony was saying "visibility everywhere except interfaces" and PSR-2 says "visibility everywhere") so @fabpot changed the rule for Symfony to follow PSR-2 (another point was the order of static and the visibility and it was also changed to follow PSR-2)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Christophe Coevoet
Collaborator

I think you are giving too much power to bundles here: a bundle becomes able to modify all the config defined explicitly by the user if it wants to do it.

I think it would be safer to give them the possibility to load an additional config file which would be prepended (so that user-defined config would still win). Giving the ability to load files means passing the loader used by the kernel, and it should then be called before calling the load method on the kernel itself (to respect the order of loaded files)

Lukas Kahwe Smith

Not sure how a config file helps solve anything. I mean they can load as many config files as they want already. The key is being able to automatically apply configuration to multiple Bundles as well as enabling/disabling features based on if certain Bundles are registered.

BTW the end result in my examples is also prepended, so that user config wins. However yes this would be up to the person implementing the Bundle. We could however provide a dedicated method for prepending in addition to or instead of setExtensionConfig.

Christophe Coevoet
Collaborator

@lsmith77 If you can load a file with the main loader, this file can provide some app-level configuration (be it for your own bundle or others).
And your code example is indeed prepending. But imagine what would occur when someone uses this feature without knowing well how the component works: he will likely call setExtensionConfig in a first implementation, thus dropping all userland config for the bundle. Your setup does not only allow to make a file win over the userland config but makes it even easier to remove the userland config.

Lukas Kahwe Smith
Christophe Coevoet
Collaborator

@lsmith77 I agree about the feature, not about the way to implement it. If you allow bundles to load a file as it it were some app-level config, they would become able to provide some config for other bundles (and you could load several files depending of which bundles are enabled), but without allowing bundles to remove the userland config.

Lukas Kahwe Smith
Lukas Kahwe Smith

@stof thought about your comments, are you suggesting for a Bundle to be able to generate a config file that is prepended? in that case the current behavior would already be that if we change setExtensionConfig to just be a prependExtensionConfig .. however i am not sure if we really need this limitation since as i point out this would still by far be less dangerous than compiler passes and also i expect this to be used mainly by open source applications on top of Symfony2 rather than standard bundles.

Lukas Kahwe Smith

@lolautruche i also think this is relevant for you guys. this way you could start preconfiguring 3rd party bundles as part of your main ezPublish bundle.

Jérôme Vieilledent

While I suspect a nice feature, the implementation looks obscure to me...

Lukas Kahwe Smith

The implementation of the example extension or the implementation of the actual changes proposed in this PR?

Lukas Kahwe Smith lsmith77 closed this October 13, 2012
Lukas Kahwe Smith lsmith77 reopened this October 13, 2012
Jérôme Vieilledent

The example, sorry :smiley:

Lukas Kahwe Smith

The example was fairly quickly hacked together. The basic thing you need to do is fetch the config for the bundle you want to change, manipulate the config (usually by appending an array to the array of configs so that you dont affect explicit configuration) and then set it again.

As I explained to @stof it would alternative/additionally be possible to support a method that pushes a config array to the top of the array of config stack. Such a method might make the necessary code simpler.

Christophe Coevoet
Collaborator

@fabpot what do you think about it ?

Johnny Robeson

I've been porting much of an existing framework over to use more symfony components and bundles. I think that this might help some of the problems i've been having. I would really appreciate some better examples as how to one would use it (same for the cmf router, but that's another story).

Lukas Kahwe Smith

not really sure what other examples i could give. the process is quite simple:
1) determine what configuration options to add to other Bundles

for example with the following code I determine that SonataAdmin for PHPCR is not installed (which means i should disable using it in other Bundles):

$bundles = $container->getParameter('kernel.bundles');
if (!isset($bundles['SonataDoctrinePHPCRAdminBundle'])) {

alternatively I could simply already process the configuration and then pick all or some of these configuration options:

$configs = $container->getExtensionConfig($this->getAlias());
$config = $this->processConfiguration(new Configuration(), $configs);

2) then add these configuration to what other Bundles I feel should get these options

usually I will add these to the top of the config array stack. this means that if the user would manually set the same setting in most cases the user setting will override what the pre-processor set.

$container->unshiftExtensionConfig($name, array('use_sonata_admin' => false));
Lukas Kahwe Smith

added ContainerBuilder::unshiftExtensionConfig since this is the usual use case. with this method added it could be discussed if ContainerBuilder::setExtensionConfig is still needed or not.

src/Symfony/Component/DependencyInjection/ContainerBuilder.php
... ...
@@ -425,6 +425,33 @@ public function getExtensionConfig($name)
425 425
     }
426 426
 
427 427
     /**
  428
+     * Prepend a config array to the configs of the given extension
  429
+     *
  430
+     * @param string $name    The name of the extension
  431
+     * @param array  $config The config to set
  432
+     *
  433
+     * @api
  434
+     */
  435
+    public function unshiftExtensionConfig($name, $config)
1
Christophe Coevoet Collaborator
stof added a note October 24, 2012

you should typehint the array

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/Symfony/Component/DependencyInjection/ContainerBuilder.php
((10 lines not shown))
  434
+     */
  435
+    public function unshiftExtensionConfig($name, $config)
  436
+    {
  437
+        $configs = $this->getExtensionConfig($name);
  438
+        $this->extensionConfigs[$name] = array_unshift($configs, $config);
  439
+    }
  440
+
  441
+    /**
  442
+     * Sets the configuration array for the given extension.
  443
+     *
  444
+     * @param string $name    The name of the extension
  445
+     * @param array  $configs The configs to set
  446
+     *
  447
+     * @api
  448
+     */
  449
+    public function setExtensionConfig($name, $configs)
2
Christophe Coevoet Collaborator
stof added a note October 24, 2012

I vote for removing this method, to avoid that a bundle remove the config set by the end user

Lukas Kahwe Smith
lsmith77 added a note October 24, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/Symfony/Component/DependencyInjection/ContainerBuilder.php
((13 lines not shown))
  437
+        if (!isset($this->extensionConfigs[$name])) {
  438
+            $this->extensionConfigs[$name] = array();
  439
+        }
  440
+
  441
+        array_unshift($this->extensionConfigs[$name], $config);
  442
+    }
  443
+
  444
+    /**
  445
+     * Sets the configuration array for the given extension.
  446
+     *
  447
+     * @param string $name    The name of the extension
  448
+     * @param array  $configs The configs to set
  449
+     *
  450
+     * @api
  451
+     */
  452
+    public function setExtensionConfig($name, array $configs)
2
Lukas Kahwe Smith
lsmith77 added a note October 28, 2012

this method should likely be removed. anything that cannot be done via prepending likely means that the given Bundle should be adjusted to handle merging of configuration.

Christophe Coevoet Collaborator
stof added a note October 28, 2012

agreed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/Symfony/Component/DependencyInjection/ContainerBuilder.php
... ...
@@ -425,6 +425,36 @@ public function getExtensionConfig($name)
425 425
     }
426 426
 
427 427
     /**
  428
+     * Prepend a config array to the configs of the given extension
  429
+     *
  430
+     * @param string $name    The name of the extension
  431
+     * @param array  $config  The config to set
  432
+     *
  433
+     * @api
1
Christophe Coevoet Collaborator
stof added a note October 28, 2012

you should not tag it as part of the stable API yet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Lukas Kahwe Smith

I spoke to @fabpot today and he said that since this patch just allows you to set defaults and not really "process" the actual configuration I shouldn't call it "preProcess" so I renamed it to "prepend".

Furthermore as its just prepending @fabpot said there isnt really a need to require manually enabling it, so here is a patch to auto-enable the prepending logic:

diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index b890fbf..7374b87 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -701,8 +701,8 @@ abstract class Kernel implements KernelInterface, TerminableInterface
      */
     protected function prependExtensionConfigs(ContainerBuilder $container)
     {
-        foreach ($this->getPrependingExtensions() as $name) {
-            $extension = $container->getExtension($name);
+        foreach ($this->bundles as $bundle) {
+            $extension = $bundle->getContainerExtension();
             if ($extension instanceof PrependExtensionInterface) {
                 $extension->prepend($container);
             }
@@ -710,16 +710,6 @@ abstract class Kernel implements KernelInterface, TerminableInterface
     }

     /**
-     * Returns the ordered list of extensions that may prepend extension configurations.
-     *
-     * @return array
-     */
-    protected function getPrependingExtensions()
-    {
-        return array();
-    }
-
-    /**
      * Gets a new ContainerBuilder instance used to build the service container.
      *
      * @return ContainerBuilder
Lukas Kahwe Smith

ok .. i pondered the code some more and now i have enabled registering of the prepending extensions by default, since its now quite easy to just override the prependExtensionConfigs() method since there is almost no logic in there anymore.

@fabpot i am not 100% sure with the naming yet ..

Lukas Kahwe Smith

@fabpot if you are ok with the PR as it is now, i can do the rebase so you can merge this?

src/Symfony/Component/DependencyInjection/ContainerBuilder.php
... ...
@@ -466,6 +466,21 @@ public function getExtensionConfig($name)
466 466
     }
467 467
 
468 468
     /**
  469
+     * Prepend a config array to the configs of the given extension
1
Fabien Potencier Owner
fabpot added a note December 05, 2012

should be Prepends and should end with a dot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/Symfony/Component/HttpKernel/DependencyInjection/PrependExtensionInterface.php
... ...
@@ -0,0 +1,24 @@
  1
+<?php
2
Fabien Potencier Owner
fabpot added a note December 05, 2012

Wouldn't be better to move this extension to the DependencyInjection component?

somehow the boarders of HttpKernel and DIC are not quite clear to me here, so I will do what you prefer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/Symfony/Component/HttpKernel/Kernel.php
... ...
@@ -649,6 +653,8 @@ protected function buildContainer()
649 653
 
650 654
         $container->addObjectResource($this);
651 655
 
  656
+        $this->prependExtensionConfigs($container, $prependingExtensions);
4
Fabien Potencier Owner
fabpot added a note December 05, 2012

Wouldn't it be better to create a compiler pass class for that logic directly in the DependencyInjection component?

or put it into MergeExtensionConfigurationPass since it seems there can only be one merge pass.

speaking of which, the phpdoc for PassConfig::getMergePass() seems out of date:
https://github.com/symfony/DependencyInjection/blob/master/Compiler/PassConfig.php#L179

one thing to note .. because of prependExtensionConfigs() it would be fairly easy for someone to decide if they for some reason do not want some extension to be able to prepend in the rare case when its not wanted. it would be harder if it would be a compiler pass unless we add some method that can filter the array of prepending extensions.

Fabien Potencier Owner
fabpot added a note December 05, 2012

Doing that in the merge extension pass seems good to me.

What you describe about the rare case does not seem like something we want to support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Lukas Kahwe Smith

@fabpot all good now? then i will squash the commits ..

Lukas Kahwe Smith

actually looking at the full change set again i am no longer sure if it makes sense to have PrependExtensionInterface in the DI rather than the HttpKernel.

src/Symfony/Component/HttpKernel/DependencyInjection/MergeExtensionConfigurationPass.php
((8 lines not shown))
27 28
     {
28 29
         $this->extensions = $extensions;
  30
+        $this->prependingExtensions = $prependingExtensions;
3
Fabien Potencier Owner
fabpot added a note December 06, 2012

What about determining the prepending extensions in the process method directly? That way, the Kernel won't have to know about them at all and everything would be part of the container.

adds overhead, but i guess we still work with the assumption that we can afford overhead during the compile phase?

and you would then want to move that logic into Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Lukas Kahwe Smith

@fabpot all good now?

Fabien Potencier fabpot commented on the diff December 07, 2012
src/Symfony/Component/HttpKernel/.gitignore
... ...
@@ -1,4 +1,5 @@
1 1
 vendor/
2 2
 composer.lock
3 3
 phpunit.xml
4  
-
  4
+Tests/ProjectContainer.php
2
Fabien Potencier Owner
fabpot added a note December 07, 2012

What is it for?

no clue .. but those two files were created when i run the unit tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Fabien Potencier
Owner

The code looks good to me now. There are two remaining task before merging:

  • Is it something we need to add somewhere in the documentation?
  • Can you add a note in the DI component CHANGELOG?

Thanks.

Lukas Kahwe Smith

i have added a changelog entry and squashed the commits.
i will also work on a documentation entry, i guess i will make it a cookbook entry. not sure if it should be included in http://symfony.com/doc/2.0/cookbook/bundles/extension.html .. but imho it would better be a separate entry.

Fabien Potencier fabpot referenced this pull request from a commit December 07, 2012
Fabien Potencier merged branch lsmith77/pre_process_app_config (PR #5566)
This PR was merged into the master branch.

Commits
-------

d7a1154 make it possible for bundles extensions to prepend settings into the application configuration of any Bundle

Discussion
----------

[2.2] add possibility for bundles extensions to prepend the app configs

Bug fix: #4652
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
License of the code: MIT

As can be seen in the patch the extensions that should prepend the configuration are enabled automatically if they implement ``PrependExtensionInterface``.

Just as an example, here an extension, which checks if SonataAdminBundle is available and if not disables integration with it in several Bundles. It also sets some default settings for ``document_class`` and ``default_document_manager_name``:
```
diff --git a/DependencyInjection/SymfonyCmfCoreExtension.php b/DependencyInjection/SymfonyCmfCoreExtension.php
index 9f92410..c0a8dbb 100644
--- a/DependencyInjection/SymfonyCmfCoreExtension.php
+++ b/DependencyInjection/SymfonyCmfCoreExtension.php
@@ -3,11 +3,12 @@
 namespace Symfony\Cmf\Bundle\CoreBundle\DependencyInjection;

 use Symfony\Component\HttpKernel\DependencyInjection\Extension;
+use Symfony\Component\\DependencyInjection\PrependExtensionInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
 use Symfony\Component\Config\FileLocator;

-class SymfonyCmfCoreExtension extends Extension
+class SymfonyCmfCoreExtension extends Extension implements PrependExtensionInterface
 {
     public function load(array $configs, ContainerBuilder $container)
     {
@@ -15,4 +16,45 @@ class SymfonyCmfCoreExtension extends Extension
         $loader->load('config.xml');
         $loader->load('services.xml');
     }
+
+    public function prepend(ContainerBuilder $container)
+    {
+        $bundles = $container->getParameter('kernel.bundles');
+        if (!isset($bundles['SonataDoctrinePHPCRAdminBundle'])) {
+            // disable SonataDoctrinePHPCRAdminBundle admin support in Bundles
+            $config = array('use_sonata_admin' => false);
+            foreach ($container->getExtensions() as $name => $extension) {
+                switch ($name) {
+                    case 'symfony_cmf_menu':
+                    case 'symfony_cmf_routing_extra':
+                    case 'symfony_cmf_simple_cms':
+                        $container->prependExtensionConfig($name, $config);
+                        break;
+                }
+            }
+        }
+
+        // process the configuration of SymfonyCmfCoreExtension
+        $configs = $container->getExtensionConfig($this->getAlias());
+        $config = $this->processConfiguration(new Configuration(), $configs);
+        // add the default configs to various Bundles
+        foreach ($container->getExtensions() as $name => $extension) {
+            switch ($name) {
+                case 'symfony_cmf_content':
+                case 'symfony_cmf_simple_cms':
+                    $container->prependExtensionConfig($name, $config);
+                    break;
+                }
+        }
+    }
 }
```

---------------------------------------------------------------------------

by stof at 2012-09-21T21:10:00Z

I think you are giving too much power to bundles here: a bundle becomes able to modify all the config defined explicitly by the user if it wants to do it.

I think it would be safer to give them the possibility to load an additional config file which would be prepended (so that user-defined config would still win). Giving the ability to load files means passing the loader used by the kernel, and it should then be called before calling the load method on the kernel itself (to respect the order of loaded files)

---------------------------------------------------------------------------

by lsmith77 at 2012-09-22T05:50:08Z

Not sure how a config file helps solve anything. I mean they can load as many config files as they want already. The key is being able to automatically apply configuration to multiple Bundles as well as enabling/disabling features based on if certain Bundles are registered.

BTW the end result in my examples is also prepended, so that user config wins. However yes this would be up to the person implementing the Bundle. We could however provide a dedicated method for prepending in addition to or instead of ``setExtensionConfig``.

---------------------------------------------------------------------------

by stof at 2012-09-22T11:40:29Z

@lsmith77 If you can load a file with the main loader, this file can provide some app-level configuration (be it for your own bundle or others).
And your code example is indeed prepending. But imagine what would occur when someone uses this feature without knowing well how the component works: he will likely call ``setExtensionConfig`` in a first implementation, thus dropping all userland config for the bundle. Your setup does not only allow to make a file win over the userland config but makes it even easier to remove the userland config.

---------------------------------------------------------------------------

by lsmith77 at 2012-09-22T18:11:29Z

but i dont get how that would help. the point is to be able for one bundle to configure other bundles before the load as this is obviously alot cleaner than trying to do the same via a compiler pass. so imho this is what is needed to encourage decoupled bundles. otherwise for example CMS or other reuseable and extensible apps will be forced to always put everything in one bundle.

---------------------------------------------------------------------------

by stof at 2012-09-22T19:23:45Z

@lsmith77 I agree about the feature, not about the way to implement it. If you allow bundles to load a file as it it were some app-level config, they would become able to provide some config for other bundles (and you could load several files depending of which bundles are enabled), but without allowing bundles to remove the userland config.

---------------------------------------------------------------------------

by lsmith77 at 2012-09-22T19:50:19Z

sorry but i dont understand what you suggest. more over i dont see the problem. its already possible to seriously break stuff with compiler passes which cannot be easily enabled/disabled. this is just convenience. if it doesnt work because of some obscure combo then simply dont use it for the app since it needs to be explicitly enabled in the kernel.

---------------------------------------------------------------------------

by lsmith77 at 2012-09-24T09:25:10Z

@stof thought about your comments, are you suggesting for a Bundle to be able to generate a config file that is prepended? in that case the current behavior would already be that if we change ``setExtensionConfig`` to just be a ``prependExtensionConfig`` .. however i am not sure if we really need this limitation since as i point out this would still by far be less dangerous than compiler passes and also i expect this to be used mainly by open source applications on top of Symfony2 rather than standard bundles.

---------------------------------------------------------------------------

by lsmith77 at 2012-10-13T13:28:29Z

@lolautruche i also think this is relevant for you guys. this way you could start preconfiguring 3rd party bundles as part of your main ezPublish bundle.

---------------------------------------------------------------------------

by lolautruche at 2012-10-13T13:57:09Z

While I suspect a nice feature, the implementation looks obscure to me...

---------------------------------------------------------------------------

by lsmith77 at 2012-10-13T17:43:02Z

The implementation of the example extension or the implementation of the actual changes proposed in this PR?

---------------------------------------------------------------------------

by lolautruche at 2012-10-13T17:46:57Z

The example, sorry :smiley:

---------------------------------------------------------------------------

by lsmith77 at 2012-10-13T17:50:38Z

The example was fairly quickly hacked together. The basic thing you need to do is fetch the config for the bundle you want to change, manipulate the config (usually by appending an array to the array of configs so that you dont affect explicit configuration) and then set it again.

As I explained to @stof it would alternative/additionally be possible to support a method that pushes a config array to the top of the array of config stack. Such a method might make the necessary code simpler.

---------------------------------------------------------------------------

by stof at 2012-10-13T21:39:07Z

@fabpot what do you think about it ?

---------------------------------------------------------------------------

by jrobeson at 2012-10-20T15:45:18Z

I've been porting much of an existing framework over to use more symfony components and bundles. I think that this might help some of the problems i've been having. I would really appreciate some better examples as how to one would use it  (same for the cmf router, but that's another story).

---------------------------------------------------------------------------

by lsmith77 at 2012-10-21T07:28:52Z

not really sure what other examples i could give. the process is quite simple:
1) determine what configuration options to add to other Bundles

for example with the following code I determine that SonataAdmin for PHPCR is not installed (which means i should disable using it in other Bundles):
```
$bundles = $container->getParameter('kernel.bundles');
if (!isset($bundles['SonataDoctrinePHPCRAdminBundle'])) {
```

alternatively I could simply already process the configuration and then pick all or some of these configuration options:
```
$configs = $container->getExtensionConfig($this->getAlias());
$config = $this->processConfiguration(new Configuration(), $configs);
```

2) then add these configuration to what other Bundles I feel should get these options

usually I will add these to the top of the config array stack. this means that if the user would manually set the same setting in most cases the user setting will override what the pre-processor set.
```
$container->unshiftExtensionConfig($name, array('use_sonata_admin' => false));
```

---------------------------------------------------------------------------

by lsmith77 at 2012-10-24T12:52:38Z

added ``ContainerBuilder::unshiftExtensionConfig`` since this is the usual use case. with this method added it could be discussed if ``ContainerBuilder::setExtensionConfig`` is still needed or not.

---------------------------------------------------------------------------

by lsmith77 at 2012-11-24T14:48:44Z

I spoke to @fabpot today and he said that since this patch just allows you to set defaults and not really "process" the actual configuration I shouldn't call it "preProcess" so I renamed it to "prepend".

Furthermore as its just prepending @fabpot said there isnt really a need to require manually enabling it, so here is a patch to auto-enable the prepending logic:
```
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index b890fbf..7374b87 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -701,8 +701,8 @@ abstract class Kernel implements KernelInterface, TerminableInterface
      */
     protected function prependExtensionConfigs(ContainerBuilder $container)
     {
-        foreach ($this->getPrependingExtensions() as $name) {
-            $extension = $container->getExtension($name);
+        foreach ($this->bundles as $bundle) {
+            $extension = $bundle->getContainerExtension();
             if ($extension instanceof PrependExtensionInterface) {
                 $extension->prepend($container);
             }
@@ -710,16 +710,6 @@ abstract class Kernel implements KernelInterface, TerminableInterface
     }

     /**
-     * Returns the ordered list of extensions that may prepend extension configurations.
-     *
-     * @return array
-     */
-    protected function getPrependingExtensions()
-    {
-        return array();
-    }
-
-    /**
      * Gets a new ContainerBuilder instance used to build the service container.
      *
      * @return ContainerBuilder
```

---------------------------------------------------------------------------

by lsmith77 at 2012-11-25T19:31:01Z

ok .. i pondered the code some more and now i have enabled registering of the prepending extensions by default, since its now quite easy to just override the ``prependExtensionConfigs()`` method since there is almost no logic in there anymore.

@fabpot i am not 100% sure with the naming yet ..

---------------------------------------------------------------------------

by lsmith77 at 2012-12-05T14:03:43Z

@fabpot if you are ok with the PR as it is now, i can do the rebase so you can merge this?

---------------------------------------------------------------------------

by lsmith77 at 2012-12-05T18:30:29Z

@fabpot all good now? then i will squash the commits ..

---------------------------------------------------------------------------

by lsmith77 at 2012-12-05T18:34:50Z

actually looking at the full change set again i am no longer sure if it makes sense to have ``PrependExtensionInterface`` in the DI rather than the HttpKernel.

---------------------------------------------------------------------------

by lsmith77 at 2012-12-07T09:21:14Z

@fabpot all good now?

---------------------------------------------------------------------------

by fabpot at 2012-12-07T09:37:52Z

The code looks good to me now. There are two remaining task before merging:

* Is it something we need to add somewhere in the documentation?
* Can you add a note in the DI component CHANGELOG?

Thanks.

---------------------------------------------------------------------------

by lsmith77 at 2012-12-07T09:49:17Z

i have added a changelog entry and squashed the commits.
i will also work on a documentation entry, i guess i will make it a cookbook entry. not sure if it should be included in http://symfony.com/doc/2.0/cookbook/bundles/extension.html .. but imho it would better be a separate entry.
79b4ca6
Fabien Potencier fabpot merged commit d7a1154 into from December 07, 2012
Fabien Potencier fabpot closed this December 07, 2012
Christophe Coevoet
Collaborator

@lsmith77 As it is also available when using the DI component standalone, I think it should go in the component doc rather than the cookbook

Drak
Collaborator

@lsmith77 - has this been documented? I didnt see a reference to a documentation ticket in the PR description.

Lukas Kahwe Smith

@drak i am working on the docs atm .. will submit a PR today.

Lukas Kahwe Smith

@stof: ok .. i will add a note in the DIC docs, but i will show a practical example in a cookbook entry.

Lukas Kahwe Smith lsmith77 referenced this pull request in symfony/symfony-docs December 07, 2012
Merged

added docs for PrependExtensionInterface #2007

mmucklo mmucklo referenced this pull request from a commit December 07, 2012
Fabien Potencier merged branch lsmith77/pre_process_app_config (PR #5566)
This PR was merged into the master branch.

Commits
-------

d7a1154 make it possible for bundles extensions to prepend settings into the application configuration of any Bundle

Discussion
----------

[2.2] add possibility for bundles extensions to prepend the app configs

Bug fix: #4652
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
License of the code: MIT

As can be seen in the patch the extensions that should prepend the configuration are enabled automatically if they implement ``PrependExtensionInterface``.

Just as an example, here an extension, which checks if SonataAdminBundle is available and if not disables integration with it in several Bundles. It also sets some default settings for ``document_class`` and ``default_document_manager_name``:
```
diff --git a/DependencyInjection/SymfonyCmfCoreExtension.php b/DependencyInjection/SymfonyCmfCoreExtension.php
index 9f92410..c0a8dbb 100644
--- a/DependencyInjection/SymfonyCmfCoreExtension.php
+++ b/DependencyInjection/SymfonyCmfCoreExtension.php
@@ -3,11 +3,12 @@
 namespace Symfony\Cmf\Bundle\CoreBundle\DependencyInjection;

 use Symfony\Component\HttpKernel\DependencyInjection\Extension;
+use Symfony\Component\\DependencyInjection\PrependExtensionInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
 use Symfony\Component\Config\FileLocator;

-class SymfonyCmfCoreExtension extends Extension
+class SymfonyCmfCoreExtension extends Extension implements PrependExtensionInterface
 {
     public function load(array $configs, ContainerBuilder $container)
     {
@@ -15,4 +16,45 @@ class SymfonyCmfCoreExtension extends Extension
         $loader->load('config.xml');
         $loader->load('services.xml');
     }
+
+    public function prepend(ContainerBuilder $container)
+    {
+        $bundles = $container->getParameter('kernel.bundles');
+        if (!isset($bundles['SonataDoctrinePHPCRAdminBundle'])) {
+            // disable SonataDoctrinePHPCRAdminBundle admin support in Bundles
+            $config = array('use_sonata_admin' => false);
+            foreach ($container->getExtensions() as $name => $extension) {
+                switch ($name) {
+                    case 'symfony_cmf_menu':
+                    case 'symfony_cmf_routing_extra':
+                    case 'symfony_cmf_simple_cms':
+                        $container->prependExtensionConfig($name, $config);
+                        break;
+                }
+            }
+        }
+
+        // process the configuration of SymfonyCmfCoreExtension
+        $configs = $container->getExtensionConfig($this->getAlias());
+        $config = $this->processConfiguration(new Configuration(), $configs);
+        // add the default configs to various Bundles
+        foreach ($container->getExtensions() as $name => $extension) {
+            switch ($name) {
+                case 'symfony_cmf_content':
+                case 'symfony_cmf_simple_cms':
+                    $container->prependExtensionConfig($name, $config);
+                    break;
+                }
+        }
+    }
 }
```

---------------------------------------------------------------------------

by stof at 2012-09-21T21:10:00Z

I think you are giving too much power to bundles here: a bundle becomes able to modify all the config defined explicitly by the user if it wants to do it.

I think it would be safer to give them the possibility to load an additional config file which would be prepended (so that user-defined config would still win). Giving the ability to load files means passing the loader used by the kernel, and it should then be called before calling the load method on the kernel itself (to respect the order of loaded files)

---------------------------------------------------------------------------

by lsmith77 at 2012-09-22T05:50:08Z

Not sure how a config file helps solve anything. I mean they can load as many config files as they want already. The key is being able to automatically apply configuration to multiple Bundles as well as enabling/disabling features based on if certain Bundles are registered.

BTW the end result in my examples is also prepended, so that user config wins. However yes this would be up to the person implementing the Bundle. We could however provide a dedicated method for prepending in addition to or instead of ``setExtensionConfig``.

---------------------------------------------------------------------------

by stof at 2012-09-22T11:40:29Z

@lsmith77 If you can load a file with the main loader, this file can provide some app-level configuration (be it for your own bundle or others).
And your code example is indeed prepending. But imagine what would occur when someone uses this feature without knowing well how the component works: he will likely call ``setExtensionConfig`` in a first implementation, thus dropping all userland config for the bundle. Your setup does not only allow to make a file win over the userland config but makes it even easier to remove the userland config.

---------------------------------------------------------------------------

by lsmith77 at 2012-09-22T18:11:29Z

but i dont get how that would help. the point is to be able for one bundle to configure other bundles before the load as this is obviously alot cleaner than trying to do the same via a compiler pass. so imho this is what is needed to encourage decoupled bundles. otherwise for example CMS or other reuseable and extensible apps will be forced to always put everything in one bundle.

---------------------------------------------------------------------------

by stof at 2012-09-22T19:23:45Z

@lsmith77 I agree about the feature, not about the way to implement it. If you allow bundles to load a file as it it were some app-level config, they would become able to provide some config for other bundles (and you could load several files depending of which bundles are enabled), but without allowing bundles to remove the userland config.

---------------------------------------------------------------------------

by lsmith77 at 2012-09-22T19:50:19Z

sorry but i dont understand what you suggest. more over i dont see the problem. its already possible to seriously break stuff with compiler passes which cannot be easily enabled/disabled. this is just convenience. if it doesnt work because of some obscure combo then simply dont use it for the app since it needs to be explicitly enabled in the kernel.

---------------------------------------------------------------------------

by lsmith77 at 2012-09-24T09:25:10Z

@stof thought about your comments, are you suggesting for a Bundle to be able to generate a config file that is prepended? in that case the current behavior would already be that if we change ``setExtensionConfig`` to just be a ``prependExtensionConfig`` .. however i am not sure if we really need this limitation since as i point out this would still by far be less dangerous than compiler passes and also i expect this to be used mainly by open source applications on top of Symfony2 rather than standard bundles.

---------------------------------------------------------------------------

by lsmith77 at 2012-10-13T13:28:29Z

@lolautruche i also think this is relevant for you guys. this way you could start preconfiguring 3rd party bundles as part of your main ezPublish bundle.

---------------------------------------------------------------------------

by lolautruche at 2012-10-13T13:57:09Z

While I suspect a nice feature, the implementation looks obscure to me...

---------------------------------------------------------------------------

by lsmith77 at 2012-10-13T17:43:02Z

The implementation of the example extension or the implementation of the actual changes proposed in this PR?

---------------------------------------------------------------------------

by lolautruche at 2012-10-13T17:46:57Z

The example, sorry :smiley:

---------------------------------------------------------------------------

by lsmith77 at 2012-10-13T17:50:38Z

The example was fairly quickly hacked together. The basic thing you need to do is fetch the config for the bundle you want to change, manipulate the config (usually by appending an array to the array of configs so that you dont affect explicit configuration) and then set it again.

As I explained to @stof it would alternative/additionally be possible to support a method that pushes a config array to the top of the array of config stack. Such a method might make the necessary code simpler.

---------------------------------------------------------------------------

by stof at 2012-10-13T21:39:07Z

@fabpot what do you think about it ?

---------------------------------------------------------------------------

by jrobeson at 2012-10-20T15:45:18Z

I've been porting much of an existing framework over to use more symfony components and bundles. I think that this might help some of the problems i've been having. I would really appreciate some better examples as how to one would use it  (same for the cmf router, but that's another story).

---------------------------------------------------------------------------

by lsmith77 at 2012-10-21T07:28:52Z

not really sure what other examples i could give. the process is quite simple:
1) determine what configuration options to add to other Bundles

for example with the following code I determine that SonataAdmin for PHPCR is not installed (which means i should disable using it in other Bundles):
```
$bundles = $container->getParameter('kernel.bundles');
if (!isset($bundles['SonataDoctrinePHPCRAdminBundle'])) {
```

alternatively I could simply already process the configuration and then pick all or some of these configuration options:
```
$configs = $container->getExtensionConfig($this->getAlias());
$config = $this->processConfiguration(new Configuration(), $configs);
```

2) then add these configuration to what other Bundles I feel should get these options

usually I will add these to the top of the config array stack. this means that if the user would manually set the same setting in most cases the user setting will override what the pre-processor set.
```
$container->unshiftExtensionConfig($name, array('use_sonata_admin' => false));
```

---------------------------------------------------------------------------

by lsmith77 at 2012-10-24T12:52:38Z

added ``ContainerBuilder::unshiftExtensionConfig`` since this is the usual use case. with this method added it could be discussed if ``ContainerBuilder::setExtensionConfig`` is still needed or not.

---------------------------------------------------------------------------

by lsmith77 at 2012-11-24T14:48:44Z

I spoke to @fabpot today and he said that since this patch just allows you to set defaults and not really "process" the actual configuration I shouldn't call it "preProcess" so I renamed it to "prepend".

Furthermore as its just prepending @fabpot said there isnt really a need to require manually enabling it, so here is a patch to auto-enable the prepending logic:
```
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index b890fbf..7374b87 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -701,8 +701,8 @@ abstract class Kernel implements KernelInterface, TerminableInterface
      */
     protected function prependExtensionConfigs(ContainerBuilder $container)
     {
-        foreach ($this->getPrependingExtensions() as $name) {
-            $extension = $container->getExtension($name);
+        foreach ($this->bundles as $bundle) {
+            $extension = $bundle->getContainerExtension();
             if ($extension instanceof PrependExtensionInterface) {
                 $extension->prepend($container);
             }
@@ -710,16 +710,6 @@ abstract class Kernel implements KernelInterface, TerminableInterface
     }

     /**
-     * Returns the ordered list of extensions that may prepend extension configurations.
-     *
-     * @return array
-     */
-    protected function getPrependingExtensions()
-    {
-        return array();
-    }
-
-    /**
      * Gets a new ContainerBuilder instance used to build the service container.
      *
      * @return ContainerBuilder
```

---------------------------------------------------------------------------

by lsmith77 at 2012-11-25T19:31:01Z

ok .. i pondered the code some more and now i have enabled registering of the prepending extensions by default, since its now quite easy to just override the ``prependExtensionConfigs()`` method since there is almost no logic in there anymore.

@fabpot i am not 100% sure with the naming yet ..

---------------------------------------------------------------------------

by lsmith77 at 2012-12-05T14:03:43Z

@fabpot if you are ok with the PR as it is now, i can do the rebase so you can merge this?

---------------------------------------------------------------------------

by lsmith77 at 2012-12-05T18:30:29Z

@fabpot all good now? then i will squash the commits ..

---------------------------------------------------------------------------

by lsmith77 at 2012-12-05T18:34:50Z

actually looking at the full change set again i am no longer sure if it makes sense to have ``PrependExtensionInterface`` in the DI rather than the HttpKernel.

---------------------------------------------------------------------------

by lsmith77 at 2012-12-07T09:21:14Z

@fabpot all good now?

---------------------------------------------------------------------------

by fabpot at 2012-12-07T09:37:52Z

The code looks good to me now. There are two remaining task before merging:

* Is it something we need to add somewhere in the documentation?
* Can you add a note in the DI component CHANGELOG?

Thanks.

---------------------------------------------------------------------------

by lsmith77 at 2012-12-07T09:49:17Z

i have added a changelog entry and squashed the commits.
i will also work on a documentation entry, i guess i will make it a cookbook entry. not sure if it should be included in http://symfony.com/doc/2.0/cookbook/bundles/extension.html .. but imho it would better be a separate entry.
8099ebe
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Dec 07, 2012
Lukas Kahwe Smith make it possible for bundles extensions to prepend settings into the …
…application configuration of any Bundle
d7a1154
This page is out of date. Refresh to see the latest.
6  src/Symfony/Component/DependencyInjection/CHANGELOG.md
Source Rendered
... ...
@@ -1,6 +1,12 @@
1 1
 CHANGELOG
2 2
 =========
3 3
 
  4
+2.2.0
  5
+-----
  6
+
  7
+ * added PrependExtensionInterface (to be able to allow extensions to prepend
  8
+   application configuration settings for any Bundle)
  9
+
4 10
 2.1.0
5 11
 -----
6 12
 
6  src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php
@@ -29,6 +29,12 @@ public function process(ContainerBuilder $container)
29 29
         $definitions = $container->getDefinitions();
30 30
         $aliases = $container->getAliases();
31 31
 
  32
+        foreach ($container->getExtensions() as $extension) {
  33
+            if ($extension instanceof PrependExtensionInterface) {
  34
+                $extension->prepend($container);
  35
+            }
  36
+        }
  37
+
32 38
         foreach ($container->getExtensions() as $name => $extension) {
33 39
             if (!$config = $container->getExtensionConfig($name)) {
34 40
                 // this extension was not called
24  src/Symfony/Component/DependencyInjection/Compiler/PrependExtensionInterface.php
... ...
@@ -0,0 +1,24 @@
  1
+<?php
  2
+
  3
+/*
  4
+ * This file is part of the Symfony package.
  5
+ *
  6
+ * (c) Fabien Potencier <fabien@symfony.com>
  7
+ *
  8
+ * For the full copyright and license information, please view the LICENSE
  9
+ * file that was distributed with this source code.
  10
+ */
  11
+
  12
+namespace Symfony\Component\DependencyInjection\Compiler;
  13
+
  14
+use Symfony\Component\DependencyInjection\ContainerBuilder;
  15
+
  16
+interface PrependExtensionInterface
  17
+{
  18
+    /**
  19
+     * Allow an extension to prepend the extension configurations.
  20
+     *
  21
+     * @param ContainerBuilder $container
  22
+     */
  23
+    public function prepend(ContainerBuilder $container);
  24
+}
15  src/Symfony/Component/DependencyInjection/ContainerBuilder.php
@@ -466,6 +466,21 @@ public function getExtensionConfig($name)
466 466
     }
467 467
 
468 468
     /**
  469
+     * Prepends a config array to the configs of the given extension.
  470
+     *
  471
+     * @param string $name    The name of the extension
  472
+     * @param array  $config  The config to set
  473
+     */
  474
+    public function prependExtensionConfig($name, array $config)
  475
+    {
  476
+        if (!isset($this->extensionConfigs[$name])) {
  477
+            $this->extensionConfigs[$name] = array();
  478
+        }
  479
+
  480
+        array_unshift($this->extensionConfigs[$name], $config);
  481
+    }
  482
+
  483
+    /**
469 484
      * Compiles the container.
470 485
      *
471 486
      * This method passes the container to compiler
22  src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
@@ -548,6 +548,28 @@ public function testThrowsExceptionWhenSetDefinitionOnAFrozenContainer()
548 548
         $container->compile();
549 549
         $container->setDefinition('a', new Definition());
550 550
     }
  551
+
  552
+    /**
  553
+     * @covers Symfony\Component\DependencyInjection\ContainerBuilder::getExtensionConfig
  554
+     * @covers Symfony\Component\DependencyInjection\ContainerBuilder::prependExtensionConfig
  555
+     */
  556
+    public function testExtensionConfig()
  557
+    {
  558
+        $container = new ContainerBuilder();
  559
+
  560
+        $configs = $container->getExtensionConfig('foo');
  561
+        $this->assertEmpty($configs);
  562
+
  563
+        $first = array('foo' => 'bar');
  564
+        $container->prependExtensionConfig('foo', $first);
  565
+        $configs = $container->getExtensionConfig('foo');
  566
+        $this->assertEquals(array($first), $configs);
  567
+
  568
+        $second = array('ding' => 'dong');
  569
+        $container->prependExtensionConfig('foo', $second);
  570
+        $configs = $container->getExtensionConfig('foo');
  571
+        $this->assertEquals(array($second, $first), $configs);
  572
+    }
551 573
 }
552 574
 
553 575
 class FooClass {}
3  src/Symfony/Component/HttpKernel/.gitignore
... ...
@@ -1,4 +1,5 @@
1 1
 vendor/
2 2
 composer.lock
3 3
 phpunit.xml
4  
-
  4
+Tests/ProjectContainer.php
  5
+Tests/classes.map
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.