From 7b4a18b04476a6cc606d083ca2d53b4cd28bd7fa Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 27 Dec 2016 18:36:50 +0100 Subject: [PATCH 1/3] [DI] Add "_defaults" key to Yaml services configuration --- .../Loader/YamlFileLoader.php | 83 +++++++++++++++---- .../Tests/Fixtures/yaml/services28.yml | 30 +++++++ .../Tests/Loader/YamlFileLoaderTest.php | 25 ++++++ 3 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services28.yml diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 0cbe6f51299d3..6bf3aa05ce9be 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -148,9 +148,56 @@ private function parseDefinitions($content, $file) if (!is_array($content['services'])) { throw new InvalidArgumentException(sprintf('The "services" key should contain an array in %s. Check your YAML syntax.', $file)); } + if (isset($content['services']['_defaults'])) { + if (!is_array($defaults = $content['services']['_defaults'])) { + throw new InvalidArgumentException(sprintf('Service defaults must be an array, "%s" given in "%s".', gettype($defaults), $file)); + } + if (isset($defaults['alias']) || isset($defaults['class']) || isset($defaults['factory'])) { + @trigger_error('Giving a service the "_defaults" name is deprecated since Symfony 3.3 and will be forbidden in 4.0. Rename your service.', E_USER_DEPRECATED); + $defaults = array(); + } else { + $defaultKeys = array('public', 'tags', 'autowire'); + unset($content['services']['_defaults']); + + foreach ($defaults as $key => $default) { + if (!in_array($key, $defaultKeys)) { + throw new InvalidArgumentException(sprintf('The configuration key "%s" cannot be used to define a default value in "%s". Allowed keys are "%s".', $key, $file, implode('", "', $defaultKeys))); + } + } + if (isset($defaults['tags'])) { + if (!is_array($tags = $defaults['tags'])) { + throw new InvalidArgumentException(sprintf('Parameter "tags" in "_defaults" must be an array in %s. Check your YAML syntax.', $file)); + } + + foreach ($tags as $tag) { + if (!is_array($tag)) { + $tag = array('name' => $tag); + } + + if (!isset($tag['name'])) { + throw new InvalidArgumentException(sprintf('A "tags" entry in "_defaults" is missing a "name" key in %s.', $file)); + } + $name = $tag['name']; + unset($tag['name']); + + if (!is_string($name) || '' === $name) { + throw new InvalidArgumentException(sprintf('The tag name in "_defaults" must be a non-empty string in %s.', $file)); + } + + foreach ($tag as $attribute => $value) { + if (!is_scalar($value) && null !== $value) { + throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type in %s. Check your YAML syntax.', $name, $attribute, $file)); + } + } + } + } + } + } else { + $defaults = array(); + } foreach ($content['services'] as $id => $service) { - $this->parseDefinition($id, $service, $file); + $this->parseDefinition($id, $service, $file, $defaults); } } @@ -160,10 +207,11 @@ private function parseDefinitions($content, $file) * @param string $id * @param array $service * @param string $file + * @param array $defaults * * @throws InvalidArgumentException When tags are invalid */ - private function parseDefinition($id, $service, $file) + private function parseDefinition($id, $service, $file, array $defaults) { if (is_string($service) && 0 === strpos($service, '@')) { $this->container->setAlias($id, substr($service, 1)); @@ -178,7 +226,7 @@ private function parseDefinition($id, $service, $file) static::checkDefinition($id, $service, $file); if (isset($service['alias'])) { - $public = !array_key_exists('public', $service) || (bool) $service['public']; + $public = array_key_exists('public', $service) ? (bool) $service['public'] : !empty($defaults['public']); $this->container->setAlias($id, new Alias($service['alias'], $public)); foreach ($service as $key => $value) { @@ -192,6 +240,7 @@ private function parseDefinition($id, $service, $file) if (isset($service['parent'])) { $definition = new ChildDefinition($service['parent']); + $defaults = array(); } else { $definition = new Definition(); } @@ -212,8 +261,9 @@ private function parseDefinition($id, $service, $file) $definition->setLazy($service['lazy']); } - if (isset($service['public'])) { - $definition->setPublic($service['public']); + $public = isset($service['public']) ? $service['public'] : (isset($defaults['public']) ? $defaults['public'] : null); + if (null !== $public) { + $definition->setPublic($public); } if (isset($service['abstract'])) { @@ -262,12 +312,13 @@ private function parseDefinition($id, $service, $file) } } - if (isset($service['tags'])) { - if (!is_array($service['tags'])) { + $tags = isset($service['tags']) ? $service['tags'] : (isset($defaults['tags']) ? $defaults['tags'] : null); + if (null !== $tags) { + if (!is_array($tags)) { throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); } - foreach ($service['tags'] as $tag) { + foreach ($tags as $tag) { if (!is_array($tag)) { $tag = array('name' => $tag); } @@ -275,14 +326,13 @@ private function parseDefinition($id, $service, $file) if (!isset($tag['name'])) { throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in %s.', $id, $file)); } + $name = $tag['name']; + unset($tag['name']); - if (!is_string($tag['name']) || '' === $tag['name']) { + if (!is_string($name) || '' === $name) { throw new InvalidArgumentException(sprintf('The tag name for service "%s" in %s must be a non-empty string.', $id, $file)); } - $name = $tag['name']; - unset($tag['name']); - foreach ($tag as $attribute => $value) { if (!is_scalar($value) && null !== $value) { throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in %s. Check your YAML syntax.', $id, $name, $attribute, $file)); @@ -303,11 +353,12 @@ private function parseDefinition($id, $service, $file) $definition->setDecoratedService($service['decorates'], $renameId, $priority); } - if (isset($service['autowire'])) { - if (is_array($service['autowire'])) { - $definition->setAutowiredMethods($service['autowire']); + $autowire = isset($service['autowire']) ? $service['autowire'] : (isset($defaults['autowire']) ? $defaults['autowire'] : null); + if (null !== $autowire) { + if (is_array($autowire)) { + $definition->setAutowiredMethods($autowire); } else { - $definition->setAutowired($service['autowire']); + $definition->setAutowired($autowire); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services28.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services28.yml new file mode 100644 index 0000000000000..c03e4e9cafba4 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services28.yml @@ -0,0 +1,30 @@ +services: + _defaults: + public: false + autowire: true + tags: + - name: foo + + with_defaults: + class: Foo + + with_null: + class: Foo + public: ~ + autowire: ~ + tags: ~ + + no_defaults: + class: Foo + public: true + autowire: false + tags: [] + + no_defaults_child: + parent: no_defaults + autowire: ~ + tags: + - name: bar + + with_defaults_child: + parent: with_defaults diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index d0390ce314f57..efa915b0f0d57 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -352,6 +352,31 @@ public function testClassFromId() $this->assertEquals(CaseSensitiveClass::class, $container->getDefinition(CaseSensitiveClass::class)->getClass()); } + public function testDefaults() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); + $loader->load('services28.yml'); + + $this->assertFalse($container->getDefinition('with_defaults')->isPublic()); + $this->assertFalse($container->getDefinition('with_null')->isPublic()); + $this->assertTrue($container->getDefinition('no_defaults')->isPublic()); + $this->assertTrue($container->getDefinition('no_defaults_child')->isPublic()); + $this->assertArrayNotHasKey('public', $container->getDefinition('no_defaults_child')->getChanges()); + + $this->assertSame(array('foo' => array(array())), $container->getDefinition('with_defaults')->getTags()); + $this->assertSame(array('foo' => array(array())), $container->getDefinition('with_null')->getTags()); + $this->assertSame(array(), $container->getDefinition('no_defaults')->getTags()); + $this->assertSame(array('bar' => array(array())), $container->getDefinition('no_defaults_child')->getTags()); + $this->assertSame(array(), $container->getDefinition('with_defaults_child')->getTags()); + + $this->assertTrue($container->getDefinition('with_defaults')->isAutowired()); + $this->assertTrue($container->getDefinition('with_null')->isAutowired()); + $this->assertFalse($container->getDefinition('no_defaults')->isAutowired()); + $this->assertFalse($container->getDefinition('no_defaults_child')->isAutowired()); + $this->assertArrayNotHasKey('autowire', $container->getDefinition('no_defaults_child')->getChanges()); + } + /** * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException * @expectedExceptionMessage The value of the "decorates" option for the "bar" service must be the id of the service without the "@" prefix (replace "@foo" with "foo"). From 05f24d5a6dace0b7994d16057fa4417f1b81fe69 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Wed, 28 Dec 2016 11:18:50 +0100 Subject: [PATCH 2/3] [DI] Add "defaults" tag to XML services configuration --- .../Loader/XmlFileLoader.php | 68 +++++++++++++++++-- .../schema/dic/services/services-1.0.xsd | 19 +++++- .../Tests/Fixtures/xml/services29.xml | 17 +++++ .../Tests/Fixtures/xml/services30.xml | 17 +++++ .../Tests/Loader/XmlFileLoaderTest.php | 34 ++++++++++ 5 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services29.xml create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services30.xml diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index ab06a0c0af18c..e77952061961a 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -118,21 +118,65 @@ private function parseDefinitions(\DOMDocument $xml, $file) } foreach ($services as $service) { - if (null !== $definition = $this->parseDefinition($service, $file)) { + if (null !== $definition = $this->parseDefinition($service, $file, $this->getServiceDefaults($xml, $file))) { $this->container->setDefinition((string) $service->getAttribute('id'), $definition); } } } + /** + * Get service defaults. + * + * @return array + */ + private function getServiceDefaults(\DOMDocument $xml, $file) + { + $xpath = new \DOMXPath($xml); + $xpath->registerNamespace('container', self::NS); + + if (null === $defaultsNode = $xpath->query('//container:services/container:defaults')->item(0)) { + return array(); + } + $defaults = array( + 'tags' => $this->getChildren($defaultsNode, 'tag'), + 'autowire' => $this->getChildren($defaultsNode, 'autowire'), + ); + + foreach ($defaults['tags'] as $tag) { + if ('' === $tag->getAttribute('name')) { + throw new InvalidArgumentException(sprintf('The tag name for tag "" in %s must be a non-empty string.', $file)); + } + } + if ($defaultsNode->hasAttribute('public')) { + $defaults['public'] = XmlUtils::phpize($defaultsNode->getAttribute('public')); + } + if (!$defaultsNode->hasAttribute('autowire')) { + foreach ($defaults['autowire'] as $k => $v) { + $defaults['autowire'][$k] = $v->textContent; + } + + return $defaults; + } + if ($defaults['autowire']) { + throw new InvalidArgumentException(sprintf('The "autowire" attribute cannot be used together with "" tags for tag "" in %s.', $file)); + } + if (XmlUtils::phpize($defaultsNode->getAttribute('autowire'))) { + $defaults['autowire'][] = '__construct'; + } + + return $defaults; + } + /** * Parses an individual Definition. * * @param \DOMElement $service * @param string $file + * @param array $defaults * * @return Definition|null */ - private function parseDefinition(\DOMElement $service, $file) + private function parseDefinition(\DOMElement $service, $file, array $defaults = array()) { if ($alias = $service->getAttribute('alias')) { $this->validateAlias($service, $file); @@ -140,6 +184,8 @@ private function parseDefinition(\DOMElement $service, $file) $public = true; if ($publicAttr = $service->getAttribute('public')) { $public = XmlUtils::phpize($publicAttr); + } elseif (isset($defaults['public'])) { + $public = $defaults['public']; } $this->container->setAlias((string) $service->getAttribute('id'), new Alias($alias, $public)); @@ -148,11 +194,18 @@ private function parseDefinition(\DOMElement $service, $file) if ($parent = $service->getAttribute('parent')) { $definition = new ChildDefinition($parent); + $defaults = array(); } else { $definition = new Definition(); } - foreach (array('class', 'shared', 'public', 'synthetic', 'lazy', 'abstract') as $key) { + if ($publicAttr = $service->getAttribute('public')) { + $definition->setPublic(XmlUtils::phpize($publicAttr)); + } elseif (isset($defaults['public'])) { + $definition->setPublic($defaults['public']); + } + + foreach (array('class', 'shared', 'synthetic', 'lazy', 'abstract') as $key) { if ($value = $service->getAttribute($key)) { $method = 'set'.$key; $definition->$method(XmlUtils::phpize($value)); @@ -216,7 +269,12 @@ private function parseDefinition(\DOMElement $service, $file) $definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument')); } - foreach ($this->getChildren($service, 'tag') as $tag) { + $tags = $this->getChildren($service, 'tag'); + if (!$tags && !empty($defaults['tags'])) { + $tags = $defaults['tags']; + } + + foreach ($tags as $tag) { $parameters = array(); foreach ($tag->attributes as $name => $node) { if ('name' === $name) { @@ -252,6 +310,8 @@ private function parseDefinition(\DOMElement $service, $file) } $definition->setAutowiredMethods($autowireTags); + } elseif (!$service->hasAttribute('autowire') && !empty($defaults['autowire'])) { + $definition->setAutowiredMethods($defaults['autowire']); } if ($value = $service->getAttribute('decorates')) { diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index 1e99d80ffe219..e3414776cf130 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -52,8 +52,9 @@ Enclosing element for the definition of all services ]]> - - + + + @@ -89,6 +90,20 @@ + + + + + + + + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services29.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services29.xml new file mode 100644 index 0000000000000..3755ba9b14a91 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services29.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services30.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services30.xml new file mode 100644 index 0000000000000..030f432b8bd26 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services30.xml @@ -0,0 +1,17 @@ + + + + + __construct + + + + + setFoo + + + setFoo + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 7fda551371a27..c25fdfb68df47 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -617,4 +617,38 @@ public function testArgumentWithKeyOutsideCollection() $this->assertSame(array('type' => 'foo', 'bar'), $container->getDefinition('foo')->getArguments()); } + + public function testDefaults() + { + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + $loader->load('services29.xml'); + + $this->assertFalse($container->getDefinition('with_defaults')->isPublic()); + $this->assertTrue($container->getDefinition('no_defaults')->isPublic()); + $this->assertTrue($container->getDefinition('no_defaults_child')->isPublic()); + $this->assertArrayNotHasKey('public', $container->getDefinition('no_defaults_child')->getChanges()); + + $this->assertSame(array('foo' => array(array())), $container->getDefinition('with_defaults')->getTags()); + $this->assertSame(array('bar' => array(array())), $container->getDefinition('no_defaults')->getTags()); + $this->assertSame(array('bar' => array(array())), $container->getDefinition('no_defaults_child')->getTags()); + $this->assertSame(array(), $container->getDefinition('with_defaults_child')->getTags()); + + $this->assertTrue($container->getDefinition('with_defaults')->isAutowired()); + $this->assertFalse($container->getDefinition('no_defaults')->isAutowired()); + $this->assertFalse($container->getDefinition('no_defaults_child')->isAutowired()); + $this->assertArrayNotHasKey('autowire', $container->getDefinition('no_defaults_child')->getChanges()); + } + + public function testDefaultsWithAutowiredMethods() + { + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + $loader->load('services30.xml'); + + $this->assertSame(array('__construct'), $container->getDefinition('with_defaults')->getAutowiredMethods()); + $this->assertSame(array('setFoo'), $container->getDefinition('no_defaults')->getAutowiredMethods()); + $this->assertSame(array('setFoo'), $container->getDefinition('no_defaults_child')->getAutowiredMethods()); + $this->assertSame(array(), $container->getDefinition('with_defaults_child')->getAutowiredMethods()); + } } From beec1cff8f65d4ac86764807c7e03625183faa12 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 28 Dec 2016 14:17:18 +0100 Subject: [PATCH 3/3] [DI] Allow definitions to inherit tags from parent context --- .../DependencyInjection/CHANGELOG.md | 12 +++++---- .../DependencyInjection/ChildDefinition.php | 25 +++++++++++++++++++ .../ResolveDefinitionTemplatesPass.php | 9 +++++++ .../Loader/XmlFileLoader.php | 20 +++++++++++++-- .../Loader/YamlFileLoader.php | 21 ++++++++++++++-- .../schema/dic/services/services-1.0.xsd | 2 ++ .../Tests/Fixtures/xml/services29.xml | 7 +++--- .../Tests/Fixtures/yaml/services28.yml | 8 ++++-- .../Tests/Loader/XmlFileLoaderTest.php | 16 +++++++----- .../Tests/Loader/YamlFileLoaderTest.php | 18 +++++++------ 10 files changed, 111 insertions(+), 27 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index 577bd28ac637b..b329162636cc2 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -4,11 +4,13 @@ CHANGELOG 3.3.0 ----- - * Add "iterator" argument type for lazy iteration over a set of values and services - - * Using the `PhpDumper` with an uncompiled `ContainerBuilder` is deprecated and - will not be supported anymore in 4.0. - + * added "iterator" argument type for lazy iteration over a set of values and services + * added "closure-proxy" argument type for turning services' methods into lazy callables + * added file-wide configurable defaults for service attributes "public", "tags", + "autowire" and a new "inherit-tags" + * made the "class" attribute optional, using the "id" as fallback + * using the `PhpDumper` with an uncompiled `ContainerBuilder` is deprecated and + will not be supported anymore in 4.0 * deprecated the `DefinitionDecorator` class in favor of `ChildDefinition` 3.2.0 diff --git a/src/Symfony/Component/DependencyInjection/ChildDefinition.php b/src/Symfony/Component/DependencyInjection/ChildDefinition.php index 4275b8adddbdc..cc85e55cf7786 100644 --- a/src/Symfony/Component/DependencyInjection/ChildDefinition.php +++ b/src/Symfony/Component/DependencyInjection/ChildDefinition.php @@ -22,6 +22,7 @@ class ChildDefinition extends Definition { private $parent; + private $inheritTags = false; private $changes = array(); /** @@ -54,6 +55,30 @@ public function getChanges() return $this->changes; } + /** + * Sets whether tags should be inherited from the parent or not. + * + * @param bool $boolean + * + * @return $this + */ + public function setInheritTags($boolean) + { + $this->inheritTags = (bool) $boolean; + + return $this; + } + + /** + * Returns whether tags should be inherited from the parent or not. + * + * @return bool + */ + public function getInheritTags() + { + return $this->inheritTags; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php index 3f854d3b8fb13..f8b1ab6464a34 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php @@ -216,6 +216,15 @@ private function doResolveDefinition(ContainerBuilder $container, ChildDefinitio $def->setShared($definition->isShared()); $def->setTags($definition->getTags()); + // append parent tags when inheriting is enabled + if ($definition->getInheritTags()) { + foreach ($parentDef->getTags() as $k => $v) { + foreach ($v as $v) { + $def->addTag($k, $v); + } + } + } + return $def; } } diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index e77952061961a..adb75d3b6a6cd 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -150,6 +150,9 @@ private function getServiceDefaults(\DOMDocument $xml, $file) if ($defaultsNode->hasAttribute('public')) { $defaults['public'] = XmlUtils::phpize($defaultsNode->getAttribute('public')); } + if ($defaultsNode->hasAttribute('inherit-tags')) { + $defaults['inherit-tags'] = XmlUtils::phpize($defaultsNode->getAttribute('inherit-tags')); + } if (!$defaultsNode->hasAttribute('autowire')) { foreach ($defaults['autowire'] as $k => $v) { $defaults['autowire'][$k] = $v->textContent; @@ -194,6 +197,12 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults = if ($parent = $service->getAttribute('parent')) { $definition = new ChildDefinition($parent); + + if ($value = $service->getAttribute('inherit-tags')) { + $definition->setInheritTags(XmlUtils::phpize($value)); + } elseif (isset($defaults['inherit-tags'])) { + $definition->setInheritTags($defaults['inherit-tags']); + } $defaults = array(); } else { $definition = new Definition(); @@ -270,8 +279,15 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults = } $tags = $this->getChildren($service, 'tag'); - if (!$tags && !empty($defaults['tags'])) { - $tags = $defaults['tags']; + + if (empty($defaults['tags'])) { + // no-op + } elseif (!$value = $service->getAttribute('inherit-tags')) { + if (!$tags) { + $tags = $defaults['tags']; + } + } elseif (XmlUtils::phpize($value)) { + $tags = array_merge($tags, $defaults['tags']); } foreach ($tags as $tag) { diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 6bf3aa05ce9be..89e82f0c086b6 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -52,6 +52,7 @@ class YamlFileLoader extends FileLoader 'configurator' => 'configurator', 'calls' => 'calls', 'tags' => 'tags', + 'inherit_tags' => 'inherit_tags', 'decorates' => 'decorates', 'decoration_inner_name' => 'decoration_inner_name', 'decoration_priority' => 'decoration_priority', @@ -156,7 +157,7 @@ private function parseDefinitions($content, $file) @trigger_error('Giving a service the "_defaults" name is deprecated since Symfony 3.3 and will be forbidden in 4.0. Rename your service.', E_USER_DEPRECATED); $defaults = array(); } else { - $defaultKeys = array('public', 'tags', 'autowire'); + $defaultKeys = array('public', 'tags', 'inherit_tags', 'autowire'); unset($content['services']['_defaults']); foreach ($defaults as $key => $default) { @@ -240,6 +241,11 @@ private function parseDefinition($id, $service, $file, array $defaults) if (isset($service['parent'])) { $definition = new ChildDefinition($service['parent']); + + $inheritTag = isset($service['inherit_tags']) ? $service['inherit_tags'] : (isset($defaults['inherit_tags']) ? $defaults['inherit_tags'] : null); + if (null !== $inheritTag) { + $definition->setInheritTags($inheritTag); + } $defaults = array(); } else { $definition = new Definition(); @@ -312,7 +318,18 @@ private function parseDefinition($id, $service, $file, array $defaults) } } - $tags = isset($service['tags']) ? $service['tags'] : (isset($defaults['tags']) ? $defaults['tags'] : null); + $tags = isset($service['tags']) ? $service['tags'] : array(); + + if (!isset($defaults['tags'])) { + // no-op + } elseif (!isset($service['inherit_tags'])) { + if (!isset($service['tags'])) { + $tags = $defaults['tags']; + } + } elseif ($service['inherit_tags']) { + $tags = array_merge($tags, $defaults['tags']); + } + if (null !== $tags) { if (!is_array($tags)) { throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index e3414776cf130..f9e9e700060ad 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -102,6 +102,7 @@ + @@ -130,6 +131,7 @@ + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services29.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services29.xml index 3755ba9b14a91..4ab511ed92dbf 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services29.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services29.xml @@ -6,12 +6,13 @@ - - + - + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services28.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services28.yml index c03e4e9cafba4..5ba7ea98f20f7 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services28.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services28.yml @@ -10,9 +10,9 @@ services: with_null: class: Foo - public: ~ + public: true autowire: ~ - tags: ~ + inherit_tags: false no_defaults: class: Foo @@ -28,3 +28,7 @@ services: with_defaults_child: parent: with_defaults + public: true + inherit_tags: true + tags: + - name: baz diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index c25fdfb68df47..659d82e2f7d11 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -625,19 +625,23 @@ public function testDefaults() $loader->load('services29.xml'); $this->assertFalse($container->getDefinition('with_defaults')->isPublic()); + $this->assertSame(array('foo' => array(array())), $container->getDefinition('with_defaults')->getTags()); + $this->assertTrue($container->getDefinition('with_defaults')->isAutowired()); + + $this->assertArrayNotHasKey('public', $container->getDefinition('no_defaults_child')->getChanges()); + $this->assertArrayNotHasKey('autowire', $container->getDefinition('no_defaults_child')->getChanges()); + + $container->compile(); + $this->assertTrue($container->getDefinition('no_defaults')->isPublic()); $this->assertTrue($container->getDefinition('no_defaults_child')->isPublic()); - $this->assertArrayNotHasKey('public', $container->getDefinition('no_defaults_child')->getChanges()); - $this->assertSame(array('foo' => array(array())), $container->getDefinition('with_defaults')->getTags()); - $this->assertSame(array('bar' => array(array())), $container->getDefinition('no_defaults')->getTags()); + $this->assertSame(array(), $container->getDefinition('no_defaults')->getTags()); $this->assertSame(array('bar' => array(array())), $container->getDefinition('no_defaults_child')->getTags()); - $this->assertSame(array(), $container->getDefinition('with_defaults_child')->getTags()); + $this->assertSame(array('baz' => array(array()), 'foo' => array(array())), $container->getDefinition('with_defaults_child')->getTags()); - $this->assertTrue($container->getDefinition('with_defaults')->isAutowired()); $this->assertFalse($container->getDefinition('no_defaults')->isAutowired()); $this->assertFalse($container->getDefinition('no_defaults_child')->isAutowired()); - $this->assertArrayNotHasKey('autowire', $container->getDefinition('no_defaults_child')->getChanges()); } public function testDefaultsWithAutowiredMethods() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index efa915b0f0d57..3d42ac34421cf 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -359,22 +359,26 @@ public function testDefaults() $loader->load('services28.yml'); $this->assertFalse($container->getDefinition('with_defaults')->isPublic()); - $this->assertFalse($container->getDefinition('with_null')->isPublic()); + $this->assertSame(array('foo' => array(array())), $container->getDefinition('with_defaults')->getTags()); + $this->assertTrue($container->getDefinition('with_defaults')->isAutowired()); + + $this->assertArrayNotHasKey('public', $container->getDefinition('no_defaults_child')->getChanges()); + $this->assertArrayNotHasKey('autowire', $container->getDefinition('no_defaults_child')->getChanges()); + + $container->compile(); + + $this->assertTrue($container->getDefinition('with_null')->isPublic()); $this->assertTrue($container->getDefinition('no_defaults')->isPublic()); $this->assertTrue($container->getDefinition('no_defaults_child')->isPublic()); - $this->assertArrayNotHasKey('public', $container->getDefinition('no_defaults_child')->getChanges()); - $this->assertSame(array('foo' => array(array())), $container->getDefinition('with_defaults')->getTags()); - $this->assertSame(array('foo' => array(array())), $container->getDefinition('with_null')->getTags()); + $this->assertSame(array(), $container->getDefinition('with_null')->getTags()); $this->assertSame(array(), $container->getDefinition('no_defaults')->getTags()); $this->assertSame(array('bar' => array(array())), $container->getDefinition('no_defaults_child')->getTags()); - $this->assertSame(array(), $container->getDefinition('with_defaults_child')->getTags()); + $this->assertSame(array('baz' => array(array()), 'foo' => array(array())), $container->getDefinition('with_defaults_child')->getTags()); - $this->assertTrue($container->getDefinition('with_defaults')->isAutowired()); $this->assertTrue($container->getDefinition('with_null')->isAutowired()); $this->assertFalse($container->getDefinition('no_defaults')->isAutowired()); $this->assertFalse($container->getDefinition('no_defaults_child')->isAutowired()); - $this->assertArrayNotHasKey('autowire', $container->getDefinition('no_defaults_child')->getChanges()); } /**