Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Extension load #589

Closed
wants to merge 3 commits into from

7 participants

Lukas Kahwe Smith John Wards Jeremy Mikola Fabien Potencier Johannes Sören Rohweder Ryan Weaver
Lukas Kahwe Smith

tag is no longer necessary, doctrine etc needs a new layer in the configs to separate dbal from orm:
app:
charset: UTF-8
error_handler: null
csrf_protection:
enabled: true
secret: xxxxxxxxxx
router: { resource: "%kernel.root_dir%/config/routing.yml" }
validation: { enabled: true, annotations: true }
templating: { engines: ['twig', 'php'] } #assets_version: SomeVersionScheme
session:
default_locale: en
lifetime: 3600
auto_start: true

# Twig Configuration
twig:
    debug:            %kernel.debug%
    strict_variables: %kernel.debug%

## Doctrine Configuration
doctrine:
    dbal:
      dbname:   test
      driver:   pdo_sqlite
      path:     %kernel.root_dir%/app.db
      logging:  %kernel.debug%

    orm:
        auto_generate_proxy_classes: %kernel.debug%
        mappings:
            HelloBundle: ~
            FOSUserBundle: ~
lsmith77 added some commits
Lukas Kahwe Smith lsmith77 implicitly load all registered bundles, all loading is now handled by…
… load(), disable loading of an extension explcitly via setting the extension config to false (for now only Yaml is implemented)
e47336e
Lukas Kahwe Smith lsmith77 only call load() if its actually callable 6d05039
Lukas Kahwe Smith lsmith77 also made things work for Xml 63a3371
John Wards

From a newbie to Symfony2, this change makes the config.yml's make much more sense.

Jeremy Mikola

There were some concerns in IRC this weekend that this change might break XML; however, Lukas proposed adding a common "config" tag in the appropriate namespace to wrap the now-second-level options that were formerly top-level tags.

<doctrine:config>
    <doctrine:orm />
    <doctrine:dbal />
</doctrine:config>

And <doctrine:config /> would be pointless, as would "doctrine: ~" in YAML, since bundles are implicitly configured just by being enabled.

So this change would require adjustments to each bundle's XSD (to add a top-level wrapping tag), but I don't see a problem.

Lukas Kahwe Smith

furthermore if a user would forget the configure doctrine, but enables it .. he would not get a proper error message telling him that he has enabled but not configured doctrine, which is of course pointless.

Lukas Kahwe Smith

btw .. i assume the XSD's would now also need to be updated, which i havent done yet ..

Fabien Potencier
Owner

Does it make sense to go one step further and only allow one extension per bundle?

If yes, why not trying to standardize the alias/namespace to the bundle name then. For instance, for FrameworkBundle, the alias would be 'framework'. For SensioBlogBundle, 'sensio_blog', ... If we do that, we will also be able to have better exception messages like "The 'SensioBlogBundle' is not enabled, but you defined settings for it under 'sensio_blog'".

Lukas Kahwe Smith

Sure.

Johannes

I'm not sure whether any of this is really necessary, it seems only to take away flexibility.

I don't think that you will have that many third party bundles where you don't have anything to configure. Naturally, since you didn't write the bundle, you most likely have to make at least some kind of configuration for it to be usable. And for app bundles which you did write, you can simply import the services.xml (@SomeBundle/Resources/services.xml) into your app config.

How about improving the installation process of bundles (which we don't have yet) to improve the user experience?

Lukas Kahwe Smith

Actually about half of my Bundles do not need any config. Furthermore right now if you add a Bundle into the kernel, there is no clear error message if that Bundle requires configuration (like the Doctrine bundle). With this change we could provide a very clear error message with instructions on the next step.

Johannes

Are these bundles third-party bundles or app bundles (written by yourself)? For app bundles, there shouldn't be a need for an Extension at all.

I think if we install a default configuration when a bundle is installed, it is much more helpful for the user than an implicit loading of the Extension. The default configuration could ship with comments and serve as a form of documentation. Similar to when you install a Debian package, you also get a default configuration where you can comment lines out, comment them in, etc.

Lukas Kahwe Smith

these are mostly app bundles. and yes there is a need for extensions for those, because they all define their own services, for controllers, for helpers etc. and many of those are indeed configurable, but always and not in all scenarios. trying to add yet another approach for configuration will just increase confusion.

as for flexibility. what is really lost? you can still pass multiple config arrays to a single load method, which can then load up any other class, split things up in multiple methods etc. so multiple extensions and extension load methods is just syntax sugar, that for the edge cases where you need it you can just implement inside the given bundle's load() method.

Johannes

The question is what are the benefits of this, and is it the best way to get them?

The benefit is not having to write "namespace.tag: ~", right? So, what I'm proposing would get you this benefit plus better documentation for actually configuring the bundle by installing a default config with inline comments. Right now, we do not have an automated installation process at all, so I'm not sure if this patch is not solving a problem that should rather be solved by such an installation process.

Lukas Kahwe Smith

what you are proposing is yet another way to do configuration instead of making the current approach easier to use.

also the special meaning of the tag is usually hard for beginners to grasp.

finally with that fabien was suggesting it also does away with the issue of non unique alias names.

Sören Rohweder

+1, good idea to get rid of empty extension.config: ~
and also the understanding for newbies would be better,I think

Ryan Weaver

I'm also behind this. If we don't do this, we'll get questions daily from users that enable a bundle and then complain that the bundle doesn't work. This is a very pragmatic approach and, as Lukas points out, the error you receive when you forget to include the bundle configuration is very poor.

I'd also second Fabien's idea to enforce one extension per bundle and rename the existing aliases to something directly related to the bundle name.

These changes will make bundles and configuration less mystifying and troublesome for users, and I don't see much "cost" to the change. I'm really hoping we can make this work.

Thanks!

Johannes

I'm not generally against this. All I'm saying is that we should probably wait until the bundle installation/distribution system that currently is being worked on is in place, and then revisit if we still need this change, or if it can be handled better there.

Having a default configuration installed automatically would also go more into the direction of good documentation, and you wouldn't have to copy/paste anymore.

Lukas Kahwe Smith

i dont think there is much sense in waiting. first up the distribution system is a big change and i am not 100% we will be able to deliver it for RC1, nor do i personally think its critical for a 2.0.

but more importantly i do not see how the proposed changes would become irrelevant. actually this is a simplification that will make whatever tools we might come up with to install + activate bundles easier. so this is all more a reason to do it now, rather than later.

Fabien Potencier
Owner

@lsmith77: At first glance, the tests do not pass. Can you check that before I work on the other changes we've talked about in this discussion? Thanks.

Lukas Kahwe Smith

yeah .. i didnt update the tests at all. will work on that tomorrow before lunch, unless someone else steps in beforehand :)

Fabien Potencier
Owner

@lsmith77: nevermind, I'm working on it right now, and with the other changes, it will be much easier for me to do evrything.

Fabien Potencier
Owner

ok, I've fixed everything. I will now update the doc and the sandbox and merge.

Fabien Potencier
Owner

merged.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 5, 2011
  1. Lukas Kahwe Smith

    implicitly load all registered bundles, all loading is now handled by…

    lsmith77 authored
    … load(), disable loading of an extension explcitly via setting the extension config to false (for now only Yaml is implemented)
  2. Lukas Kahwe Smith
  3. Lukas Kahwe Smith
This page is out of date. Refresh to see the latest.
Showing with 91 additions and 80 deletions.
  1. +24 −2 src/Symfony/Bundle/DoctrineBundle/DependencyInjection/DoctrineExtension.php
  2. +1 −4 src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddClassesToAutoloadMapPass.php
  3. +1 −4 src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddClassesToCachePass.php
  4. +1 −1  src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
  5. +6 −8 src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
  6. +1 −1  src/Symfony/Bundle/SwiftmailerBundle/DependencyInjection/SwiftmailerExtension.php
  7. +1 −1  src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php
  8. +1 −1  src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php
  9. +1 −1  src/Symfony/Bundle/ZendBundle/DependencyInjection/ZendExtension.php
  10. +8 −8 src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php
  11. +37 −9 src/Symfony/Component/DependencyInjection/ContainerBuilder.php
  12. +0 −18 src/Symfony/Component/DependencyInjection/Extension/Extension.php
  13. +1 −4 src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php
  14. +1 −1  src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
  15. +7 −17 src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
26 src/Symfony/Bundle/DoctrineBundle/DependencyInjection/DoctrineExtension.php
View
@@ -29,6 +29,28 @@
*/
class DoctrineExtension extends AbstractDoctrineExtension
{
+ public function load(array $configs, ContainerBuilder $container)
+ {
+ $dbal = $orm = array();
+ foreach ($configs as $config) {
+ if (isset($config['dbal'])) {
+ $dbal[] = $config['dbal'];
+ }
+
+ if (isset($config['orm'])) {
+ $orm[] = $config['orm'];
+ }
+ }
+
+ if (!empty($dbal)) {
+ $this->dbalLoad($dbal, $container);
+ }
+
+ if (!empty($orm)) {
+ $this->ormLoad($orm, $container);
+ }
+ }
+
/**
* Loads the DBAL configuration.
*
@@ -39,7 +61,7 @@ class DoctrineExtension extends AbstractDoctrineExtension
* @param array $config An array of configuration settings
* @param ContainerBuilder $container A ContainerBuilder instance
*/
- public function dbalLoad(array $configs, ContainerBuilder $container)
+ protected function dbalLoad(array $configs, ContainerBuilder $container)
{
$loader = new XmlFileLoader($container, __DIR__.'/../Resources/config');
$loader->load('dbal.xml');
@@ -229,7 +251,7 @@ protected function loadDbalConnection(array $connection, ContainerBuilder $conta
* @param array $config An array of configuration settings
* @param ContainerBuilder $container A ContainerBuilder instance
*/
- public function ormLoad(array $configs, ContainerBuilder $container)
+ protected function ormLoad(array $configs, ContainerBuilder $container)
{
$loader = new XmlFileLoader($container, __DIR__.'/../Resources/config');
$loader->load('orm.xml');
5 src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddClassesToAutoloadMapPass.php
View
@@ -28,10 +28,7 @@ class AddClassesToAutoloadMapPass implements CompilerPassInterface
public function process(ContainerBuilder $container)
{
$classes = array();
- foreach ($container->getExtensionConfigs() as $name => $configs) {
- list($namespace, $tag) = explode(':', $name);
-
- $extension = $container->getExtension($namespace);
+ foreach ($container->getExtensions() as $extension) {
if ($extension instanceof Extension) {
$classes = array_merge($classes, $extension->getAutoloadClassMap());
}
5 src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddClassesToCachePass.php
View
@@ -28,10 +28,7 @@ class AddClassesToCachePass implements CompilerPassInterface
public function process(ContainerBuilder $container)
{
$classes = array();
- foreach ($container->getExtensionConfigs() as $name => $configs) {
- list($namespace, $tag) = explode(':', $name);
-
- $extension = $container->getExtension($namespace);
+ foreach ($container->getExtensions() as $extension) {
if ($extension instanceof Extension) {
$classes = array_merge($classes, $extension->getClassesToCompile());
}
2  src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
View
@@ -29,7 +29,7 @@
*/
class FrameworkExtension extends Extension
{
- public function configLoad(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container)
{
foreach ($configs as $config) {
$this->doConfigLoad($config, $container);
14 src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
View
@@ -35,17 +35,15 @@ class SecurityExtension extends Extension
protected $contextListeners = array();
protected $listenerPositions = array('pre_auth', 'form', 'http', 'remember_me');
- public function configLoad(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container)
{
foreach ($configs as $config) {
- $this->doConfigLoad($this->normalizeKeys($config), $container);
- }
- }
+ if (isset($config['acl'])) {
+ $this->doAclLoad($this->normalizeKeys($config['acl']), $container);
+ unset($config['acl']);
+ }
- public function aclLoad(array $configs, ContainerBuilder $container)
- {
- foreach ($configs as $config) {
- $this->doAclLoad($this->normalizeKeys($config), $container);
+ $this->doConfigLoad($this->normalizeKeys($config), $container);
}
}
2  src/Symfony/Bundle/SwiftmailerBundle/DependencyInjection/SwiftmailerExtension.php
View
@@ -23,7 +23,7 @@
*/
class SwiftMailerExtension extends Extension
{
- public function configLoad(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container)
{
foreach ($configs as $config) {
$this->doConfigLoad($config, $container);
2  src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php
View
@@ -23,7 +23,7 @@
*/
class TwigExtension extends Extension
{
- public function configLoad(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container)
{
$loader = new XmlFileLoader($container, __DIR__.'/../Resources/config');
$loader->load('twig.xml');
2  src/Symfony/Bundle/WebProfilerBundle/DependencyInjection/WebProfilerExtension.php
View
@@ -32,7 +32,7 @@
*/
class WebProfilerExtension extends Extension
{
- public function configLoad(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container)
{
foreach ($configs as $config) {
$this->doConfigLoad($config, $container);
2  src/Symfony/Bundle/ZendBundle/DependencyInjection/ZendExtension.php
View
@@ -34,7 +34,7 @@ class ZendExtension extends Extension
* @param array $config An array of configuration settings
* @param ContainerBuilder $container A ContainerBuilder instance
*/
- public function configLoad(array $configs, ContainerBuilder $container)
+ public function load(array $configs, ContainerBuilder $container)
{
$loader = new XmlFileLoader($container, __DIR__.'/../Resources/config');
$loader->load('logger.xml');
16 src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php
View
@@ -29,17 +29,17 @@ public function process(ContainerBuilder $container)
$definitions = $container->getDefinitions();
$aliases = $container->getAliases();
- foreach ($container->getExtensionConfigs() as $name => $configs) {
- list($namespace, $tag) = explode(':', $name);
+ foreach ($container->getExtensions() as $alias => $extension) {
- $extension = $container->getExtension($namespace);
+ $configs = $container->getExtensionConfig($alias);
+ if ($configs !== false && method_exists($extension, 'load')) {
+ $tmpContainer = new ContainerBuilder($container->getParameterBag());
+ $tmpContainer->addObjectResource($extension);
- $tmpContainer = new ContainerBuilder($container->getParameterBag());
- $tmpContainer->addObjectResource($extension);
+ $extension->load($configs, $tmpContainer);
- $extension->load($tag, $configs, $tmpContainer);
-
- $container->merge($tmpContainer);
+ $container->merge($tmpContainer);
+ }
}
$container->addDefinitions($definitions);
46 src/Symfony/Component/DependencyInjection/ContainerBuilder.php
View
@@ -28,6 +28,7 @@
class ContainerBuilder extends Container implements TaggedContainerInterface
{
static protected $extensions = array();
+ static protected $extensionsByNs = array();
protected $definitions = array();
protected $aliases = array();
@@ -53,7 +54,7 @@ public function __construct(ParameterBagInterface $parameterBag = null)
*/
static public function registerExtension(ExtensionInterface $extension)
{
- static::$extensions[$extension->getAlias()] = static::$extensions[$extension->getNamespace()] = $extension;
+ static::$extensions[$extension->getAlias()] = static::$extensionsByNs[$extension->getNamespace()] = $extension;
}
/**
@@ -65,16 +66,30 @@ static public function registerExtension(ExtensionInterface $extension)
*/
static public function getExtension($name)
{
- if (!isset(static::$extensions[$name])) {
- throw new \LogicException(sprintf('Container extension "%s" is not registered', $name));
+ if (empty(static::$extensions[$name])) {
+ if (empty(static::$extensionsByNs[$name])) {
+ throw new \LogicException(sprintf('Container extension "%s" is not registered', $name));
+ }
+
+ return static::$extensionsByNs[$name];
}
return static::$extensions[$name];
}
+ /**
+ * Returns extensions keyed by alias
+ *
+ * @return array ExtensionInterfaces
+ */
+ static public function getExtensions()
+ {
+ return static::$extensions;
+ }
+
static public function hasExtension($name)
{
- return isset(static::$extensions[$name]);
+ return isset(static::$extensions[$name]) || isset(static::$extensionsByNs[$name]);
}
/**
@@ -118,12 +133,11 @@ public function addObjectResource($object)
* Loads the configuration for an extension.
*
* @param string $extension The extension alias or namespace
- * @param string $tag The extension tag to load (without the namespace - namespace.tag)
* @param array $values An array of values that customizes the extension
*
* @return ContainerBuilder The current instance
*/
- public function loadFromExtension($extension, $tag, array $values = array())
+ public function loadFromExtension($extension, array $values = array())
{
if (true === $this->isFrozen()) {
throw new \LogicException('Cannot load from an extension on a frozen container.');
@@ -131,11 +145,11 @@ public function loadFromExtension($extension, $tag, array $values = array())
$namespace = $this->getExtension($extension)->getAlias();
- if (!isset($this->extensionConfigs[$namespace.':'.$tag])) {
- $this->extensionConfigs[$namespace.':'.$tag] = array();
+ if (!isset($this->extensionConfigs[$namespace])) {
+ $this->extensionConfigs[$namespace] = array();
}
- $this->extensionConfigs[$namespace.':'.$tag][] = $this->getParameterBag()->resolveValue($values);
+ $this->extensionConfigs[$namespace][] = $this->getParameterBag()->resolveValue($values);
return $this;
}
@@ -341,6 +355,20 @@ public function getExtensionConfigs()
}
/**
+ * Returns the containers for the registered extensions by alias.
+ *
+ * @return ExtensionInterface extension container
+ */
+ public function getExtensionConfig($name)
+ {
+ if (empty($this->extensionConfigs[$name])) {
+ return array(array());
+ }
+
+ return $this->extensionConfigs[$name];
+ }
+
+ /**
* Sets the extension configs array
*
* @param array $config
18 src/Symfony/Component/DependencyInjection/Extension/Extension.php
View
@@ -21,24 +21,6 @@
abstract class Extension implements ExtensionInterface
{
/**
- * Loads a specific configuration.
- *
- * @param string $tag The tag name
- * @param array $config An array of configuration values
- * @param ContainerBuilder $configuration A ContainerBuilder instance
- *
- * @throws \InvalidArgumentException When provided tag is not defined in this extension
- */
- public function load($tag, array $config, ContainerBuilder $configuration)
- {
- if (!method_exists($this, $method = $tag.'Load')) {
- throw new \InvalidArgumentException(sprintf('The tag "%s:%s" is not defined in the "%s" extension.', $this->getAlias(), $tag, $this->getAlias()));
- }
-
- $this->$method($config, $configuration);
- }
-
- /**
* This method normalizes keys between the different configuration formats
*
* Namely, you mostly have foo_bar in YAML while you have foo-bar in XML.
5 src/Symfony/Component/DependencyInjection/Extension/ExtensionInterface.php
View
@@ -23,15 +23,12 @@
/**
* Loads a specific configuration.
*
- * @param string $tag The tag name
* @param array $config An array of configuration values
* @param ContainerBuilder $configuration A ContainerBuilder instance
*
- * @return ContainerBuilder A ContainerBuilder instance
- *
* @throws \InvalidArgumentException When provided tag is not defined in this extension
*/
- function load($tag, array $config, ContainerBuilder $configuration);
+ function load(array $config, ContainerBuilder $configuration);
/**
* Returns the namespace to be used for this extension (XML namespace).
2  src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
View
@@ -377,7 +377,7 @@ protected function loadFromExtensions(SimpleXMLElement $xml)
$values = array();
}
- $this->container->loadFromExtension($node->namespaceURI, $node->localName, $values);
+ $this->container->loadFromExtension($node->namespaceURI, $values);
}
}
24 src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
View
@@ -225,22 +225,14 @@ protected function validate($content, $file)
throw new \InvalidArgumentException(sprintf('The service file "%s" is not valid.', $file));
}
- foreach (array_keys($content) as $key) {
- if (in_array($key, array('imports', 'parameters', 'services', 'interfaces'))) {
+ foreach (array_keys($content) as $namespace) {
+ if (in_array($namespace, array('imports', 'parameters', 'services', 'interfaces'))) {
continue;
}
- // can it be handled by an extension?
- if (false !== strpos($key, '.')) {
- list($namespace, $tag) = explode('.', $key);
- if (!$this->container->hasExtension($namespace)) {
- throw new \InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in %s).', $key, $file));
- }
-
- continue;
+ if (!$this->container->hasExtension($namespace)) {
+ throw new \InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in %s).', $key, $file));
}
-
- throw new \InvalidArgumentException(sprintf('The "%s" tag is not valid (in %s).', $key, $file));
}
return $content;
@@ -274,18 +266,16 @@ protected function resolveServices($value)
protected function loadFromExtensions($content)
{
- foreach ($content as $key => $values) {
- if (in_array($key, array('imports', 'parameters', 'services', 'interfaces'))) {
+ foreach ($content as $namespace => $values) {
+ if (in_array($namespace, array('imports', 'parameters', 'services', 'interfaces'))) {
continue;
}
- list($namespace, $tag) = explode('.', $key);
-
if (!is_array($values)) {
$values = array();
}
- $this->container->loadFromExtension($namespace, $tag, $values);
+ $this->container->loadFromExtension($namespace, $values);
}
}
}
Something went wrong with that request. Please try again.