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 ab06a0c0af18c..adb75d3b6a6cd 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -118,21 +118,68 @@ 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('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; + } + + 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 +187,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 +197,24 @@ private function parseDefinition(\DOMElement $service, $file) 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(); } - 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 +278,19 @@ 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 (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) { $parameters = array(); foreach ($tag->attributes as $name => $node) { if ('name' === $name) { @@ -252,6 +326,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/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 0cbe6f51299d3..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', @@ -148,9 +149,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', 'inherit_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 +208,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 +227,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 +241,12 @@ private function parseDefinition($id, $service, $file) 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(); } @@ -212,8 +267,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 +318,24 @@ private function parseDefinition($id, $service, $file) } } - if (isset($service['tags'])) { - if (!is_array($service['tags'])) { + $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)); } - foreach ($service['tags'] as $tag) { + foreach ($tags as $tag) { if (!is_array($tag)) { $tag = array('name' => $tag); } @@ -275,14 +343,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 +370,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/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index 1e99d80ffe219..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 @@ -52,8 +52,9 @@ Enclosing element for the definition of all services ]]> - - + + + @@ -89,6 +90,21 @@ + + + + + + + + + + + + + @@ -115,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 new file mode 100644 index 0000000000000..4ab511ed92dbf --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services29.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + 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/Fixtures/yaml/services28.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services28.yml new file mode 100644 index 0000000000000..5ba7ea98f20f7 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services28.yml @@ -0,0 +1,34 @@ +services: + _defaults: + public: false + autowire: true + tags: + - name: foo + + with_defaults: + class: Foo + + with_null: + class: Foo + public: true + autowire: ~ + inherit_tags: false + + 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 + 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 7fda551371a27..659d82e2f7d11 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -617,4 +617,42 @@ 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->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->assertSame(array(), $container->getDefinition('no_defaults')->getTags()); + $this->assertSame(array('bar' => array(array())), $container->getDefinition('no_defaults_child')->getTags()); + $this->assertSame(array('baz' => array(array()), 'foo' => array(array())), $container->getDefinition('with_defaults_child')->getTags()); + + $this->assertFalse($container->getDefinition('no_defaults')->isAutowired()); + $this->assertFalse($container->getDefinition('no_defaults_child')->isAutowired()); + } + + 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()); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index d0390ce314f57..3d42ac34421cf 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -352,6 +352,35 @@ 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->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->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('baz' => array(array()), 'foo' => array(array())), $container->getDefinition('with_defaults_child')->getTags()); + + $this->assertTrue($container->getDefinition('with_null')->isAutowired()); + $this->assertFalse($container->getDefinition('no_defaults')->isAutowired()); + $this->assertFalse($container->getDefinition('no_defaults_child')->isAutowired()); + } + /** * @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"). pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy