8000 feature #21071 [DI] Add "inherit-tags" with configurable defaults + s… · symfony/symfony@e317a12 · GitHub
[go: up one dir, main page]

Skip to content

Commit e317a12

Browse files
committed
feature #21071 [DI] Add "inherit-tags" with configurable defaults + same for "public", "tags" & "autowire" (nicolas-grekas, ogizanagi)
This PR was merged into the 3.3-dev branch. Discussion ---------- [DI] Add "inherit-tags" with configurable defaults + same for "public", "tags" & "autowire" | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #20048 | License | MIT | Doc PR | - Instead of making services private by default (#20048), which is going to create a big migration burden that might not be worth it, I'd like to propose the idea of changing the default for the current file. Having a place to redefine some defaults, this can also be used to enable autowiring for a file, making it much lighter in the end. This PR handles defaults for "public", "autowired" and "tags". Not sure the other options need a configurable default. Please comment if you think otherwise. Short example (the feat is more interesting with a longer list of services): ```yaml services: _defaults: public: false autowire: ['_construct', 'set*'] foo: class: Foo ``` ```xml <?xml version="1.0" encoding="utf-8"?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <defaults public="false"> <autowire>__construct</autowire> <tag name="foo"/> </defaults> </services> </container> ``` ping @dunglas @weaverryan Commits ------- beec1cf [DI] Allow definitions to inherit tags from parent context 05f24d5 [DI] Add "defaults" tag to XML services configuration 7b4a18b [DI] Add "_defaults" key to Yaml services configuration
2 parents 306a060 + beec1cf commit e317a12

File tree

11 files changed

+360
-27
lines changed

11 files changed

+360
-27
lines changed

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ CHANGELOG
44
3.3.0
55
-----
66

7-
* Add "iterator" argument type for lazy iteration over a set of values and services
8-
9-
* Using the `PhpDumper` with an uncompiled `ContainerBuilder` is deprecated and
10-
will not be supported anymore in 4.0.
11-
7+
* added "iterator" argument type for lazy iteration over a set of values and services
8+
* added "closure-proxy" argument type for turning services' methods into lazy callables
9+
* added file-wide configurable defaults for service attributes "public", "tags",
10+
"autowire" and a new "inherit-tags"
11+
* made the "class" attribute optional, using the "id" as fallback
12+
* using the `PhpDumper` with an uncompiled `ContainerBuilder` is deprecated and
13+
will not be supported anymore in 4.0
1214
* deprecated the `DefinitionDecorator` class in favor of `ChildDefinition`
1315

1416
3.2.0

src/Symfony/Component/DependencyInjection/ChildDefinition.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
class ChildDefinition extends Definition
2323
{
2424
private $parent;
25+
private $inheritTags = false;
2526
private $changes = array();
2627

2728
/**
@@ -54,6 +55,30 @@ public function getChanges()
5455
return $this->changes;
5556
}
5657

58+
/**
59+
* Sets whether tags should be inherited from the parent or not.
60+
*
61+
* @param bool $boolean
62+
*
63+
* @return $this
64+
*/
65+
public function setInheritTags($boolean)
66+
{
67+
$this->inheritTags = (bool) $boolean;
68+
69+
return $this;
70+
}
71+
72+
/**
73+
* Returns whether tags should be inherited from the parent or not.
74+
*
75+
* @return bool
76+
*/
77+
public function getInheritTags()
78+
{
79+
return $this->inheritTags;
80+
}
81+
5782
/**
5883
* {@inheritdoc}
5984
*/

src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,15 @@ private function doResolveDefinition(ContainerBuilder $container, ChildDefinitio
216216
$def->setShared($definition->isShared());
217217
$def->setTags($definition->getTags());
218218

219+
// append parent tags when inheriting is enabled
220+
if ($definition->getInheritTags()) {
221+
foreach ($parentDef->getTags() as $k => $v) {
222+
foreach ($v as $v) {
223+
$def->addTag($k, $v);
224+
}
225+
}
226+
}
227+
219228
return $def;
220229
}
221230
}

src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,28 +118,77 @@ private function parseDefinitions(\DOMDocument $xml, $file)
118118
}
119119

120120
foreach ($services as $service) {
121-
if (null !== $definition = $this->parseDefinition($service, $file)) {
121+
if (null !== $definition = $this->parseDefinition($service, $file, $this->getServiceDefaults($xml, $file))) {
122122
$this->container->setDefinition((string) $service->getAttribute('id'), $definition);
123123
}
124124
}
125125
}
126126

127+
/**
128+
* Get service defaults.
129+
*
130+
* @return array
131+
*/
132+
private function getServiceDefaults(\DOMDocument $xml, $file)
133+
{
134+
$xpath = new \DOMXPath($xml);
135+
$xpath->registerNamespace('container', self::NS);
136+
137+
if (null === $defaultsNode = $xpath->query('//container:services/container:defaults')->item(0)) {
138+
return array();
139+
}
140+
$defaults = array(
141+
'tags' => $this->getChildren($defaultsNode, 'tag'),
142+
'autowire' => $this->getChildren($defaultsNode, 'autowire'),
143+
);
144+
145+
foreach ($defaults['tags'] as $tag) {
146+
if ('' === $tag->getAttribute('name')) {
147+
throw new InvalidArgumentException(sprintf('The tag name for tag "<defaults>" in %s must be a non-empty string.', $file));
148+
}
149+
}
150+
if ($defaultsNode->hasAttribute('public')) {
151+
$defaults['public'] = XmlUtils::phpize($defaultsNode->getAttribute('public'));
152+
}
153+
if ($defaultsNode->hasAttribute('inherit-tags')) {
154+
$defaults['inherit-tags'] = XmlUtils::phpize($defaultsNode->getAttribute('inherit-tags'));
155+
}
156+
if (!$defaultsNode->hasAttribute('autowire')) {
157+
foreach ($defaults['autowire'] as $k => $v) {
158+
$defaults['autowire'][$k] = $v->textContent;
159+
}
160+
161+
return $defaults;
162+
}
163+
if ($defaults['autowire']) {
164+
throw new InvalidArgumentException(sprintf('The "autowire" attribute cannot be used together with "<autowire>" tags for tag "<defaults>" in %s.', $file));
165+
}
166+
if (XmlUtils::phpize($defaultsNode->getAttribute('autowire'))) {
167+
$defaults['autowire'][] = '__construct';
168+
}
169+
170+
return $defaults;
171+
}
172+
127173
/**
128174
* Parses an individual Definition.
129175
*
130176
* @param \DOMElement $service
131177
* @param string $file
178+
* @param array $defaults
132179
*
133180
* @return Definition|null
134181
*/
135-
private function parseDefinition(\DOMElement $service, $file)
182+
private function parseDefinition(\DOMElement $service, $file, array $defaults = array())
136183
{
137184
if ($alias = $service->getAttribute('alias')) {
138185
$this->validateAlias($service, $file);
139186

140187
$public = true;
141188
if ($publicAttr = $service->getAttribute('public')) {
142189
$public = XmlUtils::phpize($publicAttr);
190+
} elseif (isset($defaults['public'])) {
191+
$public = $defaults['public'];
143192
}
144193
$this->container->setAlias((string) $service->getAttribute('id'), new Alias($alias, $public));
145194

@@ -148,11 +197,24 @@ private function parseDefinition(\DOMElement $service, $file)
148197

149198
if ($parent = $service->getAttribute('parent')) {
150199
$definition = new ChildDefinition($parent);
200+
201+
if ($value = $service->getAttribute('inherit-tags')) {
202+
$definition->setInheritTags(XmlUtils::phpize($value));
203+
} elseif (isset($defaults['inherit-tags'])) {
204+
$definition->setInheritTags($defaults['inherit-tags']);
205+
}
206+
$defaults = array();
151207
} else {
152208
$definition = new Definition();
153209
}
154210

155-
foreach (array('class', 'shared', 'public', 'synthetic', 'lazy', 'abstract') as $key) {
211+
if ($publicAttr = $service->getAttribute('public')) {
212+
$definition->setPublic(XmlUtils::phpize($publicAttr));
213+
} elseif (isset($defaults['public'])) {
214+
$definition->setPublic($defaults['public']);
215+
}
216+
217+
foreach (array('class', 'shared', 'synthetic', 'lazy', 'abstract') as $key) {
156218
if ($value = $service->getAttribute($key)) {
157219
$method = 'set'.$key;
158220
$definition->$method(XmlUtils::phpize($value));
@@ -216,7 +278,19 @@ private function parseDefinition(\DOMElement $service, $file)
216278
$definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument'));
217279
}
218280

219-
foreach ($this->getChildren($service, 'tag') as $tag) {
281+
$tags = $this->getChildren($service, 'tag');
282+
283+
if (empty($defaults['tags'])) {
284+
// no-op
285+
} elseif (!$value = $service->getAttribute('inherit-tags')) {
286+
if (!$tags) {
287+
$tags = $defaults['tags'];
288+
}
289+
} elseif (XmlUtils::phpize($value)) {
290+
$tags = array_merge($tags, $defaults['tags']);
291+
}
292+
293+
foreach ($tags as $tag) {
220294
$parameters = array();
221295
foreach ($tag->attributes as $name => $node) {
222296
if ('name' === $name) {
@@ -252,6 +326,8 @@ private function parseDefinition(\DOMElement $service, $file)
252326
}
253327

254328
$definition->setAutowiredMethods($autowireTags);
329+
} elseif (!$service->hasAttribute('autowire') && !empty($defaults['autowire'])) {
330+
$definition->setAutowiredMethods($defaults['autowire']);
255331
}
256332

257333
if ($value = $service->getAttribute('decorates')) {

src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class YamlFileLoader extends FileLoader
5252
'configurator' => 'configurator',
5353
'calls' => 'calls',
5454
'tags' => 'tags',
55+
'inherit_tags' => 'inherit_tags',
5556
'decorates' => 'decorates',
5657
'decoration_inner_name' => 'decoration_inner_name',
5758
'decoration_priority' => 'decoration_priority',
@@ -148,9 +149,56 @@ private function parseDefinitions($content, $file)
148149
if (!is_array($content['services'])) {
149150
throw new InvalidArgumentException(sprintf('The "services" key should contain an array in %s. Check your YAML syntax.', $file));
150151
}
152+
if (isset($content['services']['_defaults'])) {
153+
if (!is_array($defaults = $content['services']['_defaults'])) {
154+
throw new InvalidArgumentException(sprintf('Service defaults must be an array, "%s" given in "%s".', gettype($defaults), $file));
155+
}
156+
if (isset($defaults['alias']) || isset($defaults['class']) || isset($defaults['factory'])) {
157+
@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);
158+
$defaults = array();
159+
} else {
160+
$defaultKeys = array('public', 'tags', 'inherit_tags', 'autowire');
161+
unset($content['services']['_defaults']);
162+
163+
foreach ($defaults as $key => $default) {
164+
if (!in_array($key, $defaultKeys)) {
165+
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)));
166+
}
167+
}
168+
if (isset($defaults['tags'])) {
169+
if (!is_array($tags = $defaults['tags'])) {
170+
throw new InvalidArgumentException(sprintf('Parameter "tags" in "_defaults" must be an array in %s. Check your YAML syntax.', $file));
171+
}
172+
173+
foreach ($tags as $tag) {
174+
if (!is_array($tag)) {
175+
$tag = array('name' => $tag);
176+
}
177+
178+
if (!isset($tag['name'])) {
179+
throw new InvalidArgumentException(sprintf('A "tags" entry in "_defaults" is missing a "name" key in %s.', $file));
180+
}
181+
$name = $tag['name'];
182+
unset($tag['name']);
183+
184+
if (!is_string($name) || '' === $name) {
185+
throw new InvalidArgumentException(sprintf('The tag name in "_defaults" must be a non-empty string in %s.', $file));
186+
}
187+
188+
foreach ($tag as $attribute => $value) {
189+
if (!is_scalar($value) && null !== $value) {
190+
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));
191+
}
192+
}
193+
}
194+
}
195+
}
196+
} else {
197+
$defaults = array();
198+
}
151199

152200
foreach ($content['services'] as $id => $service) {
153-
$this->parseDefinition($id, $service, $file);
201+
$this->parseDefinition($id, $service, $file, $defaults);
154202
}
155203
}
156204

@@ -160,10 +208,11 @@ private function parseDefinitions($content, $file)
160208
* @param string $id
161209
* @param array $service
162210
* @param string $file
211+
* @param array $defaults
163212
*
164213
* @throws InvalidArgumentException When tags are invalid
165214
*/
166-
private function parseDefinition($id, $service, $file)
215+
private function parseDefinition($id, $service, $file, array $defaults)
167216
{
168217
if (is_string($service) && 0 === strpos($service, '@')) {
169218
$this->container->setAlias($id, substr($service, 1));
@@ -178,7 +227,7 @@ private function parseDefinition($id, $service, $file)
178227
static::checkDefinition($id, $service, $file);
179228

180229
if (isset($service['alias'])) {
181-
$public = !array_key_exists('public', $service) || (bool) $service['public'];
230+
$public = array_key_exists('public', $service) ? (bool) $service['public'] : !empty($defaults['public']);
182231
$this->container->setAlias($id, new Alias($service['alias'], $public));
183232

184233
foreach ($service as $key => $value) {
@@ -192,6 +241,12 @@ private function parseDefinition($id, $service, $file)
192241

193242
if (isset($service['parent'])) {
194243
$definition = new ChildDefinition($service['parent']);
244+
245+
$inheritTag = isset($service['inherit_tags']) ? $service['inherit_tags'] : (isset($defaults['inherit_tags']) ? $defaults['inherit_tags'] : null);
246+
if (null !== $inheritTag) {
247+
$definition->setInheritTags($inheritTag);
248+
}
249+
$defaults = array();
195250
} else {
196251
$definition = new Definition();
197252
}
@@ -212,8 +267,9 @@ private function parseDefinition($id, $service, $file)
212267
$definition->setLazy($service['lazy']);
213268
}
214269

215-
if (isset($service['public'])) {
216-
$definition->setPublic($service['public']);
270+
$public = isset($service['public']) ? $service['public'] : (isset($defaults['public']) ? $defaults['public'] : null);
271+
if (null !== $public) {
272+
$definition->setPublic($public);
217273
}
218274

219275
if (isset($service['abstract'])) {
@@ -262,27 +318,38 @@ private function parseDefinition($id, $service, $file)
262318
}
263319
}
264320

265-
if (isset($service['tags'])) {
266-
if (!is_array($service['tags'])) {
321+
$tags = isset($service['tags']) ? $service['tags'] : array();
322+
323+
if (!isset($defaults['tags'])) {
324+
// no-op
325+
} elseif (!isset($service['inherit_tags'])) {
326+
if (!isset($service['tags'])) {
327+
$tags = $defaults['tags'];
328+
}
329+
} elseif ($service['inherit_tags']) {
330+
$tags = array_merge($tags, $defaults['tags']);
331+
}
332+
333+
if (null !== $tags) {
334+
if (!is_array($tags)) {
267335
throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
268336
}
269337

270-
foreach ($service['tags'] as $tag) {
338+
foreach ($tags as $tag) {
271339
if (!is_array($tag)) {
272340
$tag = array('name' => $tag);
273341
}
274342

275343
if (!isset($tag['name'])) {
276344
throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in %s.', $id, $file));
277345
}
346+
$name = $tag['name'];
347+
unset($tag['name']);
278348

279-
if (!is_string($tag['name']) || '' === $tag['name']) {
349+
if (!is_string($name) || '' === $name) {
280350
throw new InvalidArgumentException(sprintf('The tag name for service "%s" in %s must be a non-empty string.', $id, $file));
281351
}
282352

283-
$name = $tag['name'];
284-
unset($tag['name']);
285-
286353
foreach ($tag as $attribute => $value) {
287354
if (!is_scalar($value) && null !== $value) {
288355
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)
303370
$definition->setDecoratedService($service['decorates'], $renameId, $priority);
304371
}
305372

306-
if (isset($service['autowire'])) {
307-
if (is_array($service['autowire'])) {
308-
$definition->setAutowiredMethods($service['autowire']);
373+
$autowire = isset($service['autowire']) ? $service['autowire'] : (isset($defaults['autowire']) ? $defaults['autowire'] : null);
374+
if (null !== $autowire) {
375+
if (is_array($autowire)) {
376+
$definition->setAutowiredMethods($autowire);
309377
} else {
310-
$definition->setAutowired($service['autowire']);
378+
$definition->setAutowired($autowire);
311379
}
312380
}
313381

0 commit comments

Comments
 (0)
0