Skip to content

Commit 8f47316

Browse files
committed
feature #22234 [DI] Introducing autoconfigure: automatic _instanceof configuration (weaverryan)
This PR was squashed before being merged into the 3.3-dev branch (closes #22234). Discussion ---------- [DI] Introducing autoconfigure: automatic _instanceof configuration | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes (mostly, a continuation of a new feature) | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | symfony/symfony-docs#7538 This is a proposal to allow the user to opt into some automatic `_instanceof` config. Suppose I want to auto-tag all of my voters and event subscribers ```yml # current services: _defaults: autowire: true _instanceof: Symfony\Component\Security\Core\Authorization\Voter\VoterInterface: tags: [security.voter] Symfony\Component\EventDispatcher\EventSubscriberInterface: tags: [kernel.event_subscriber] # services using the above tags AppBundle\Security\PostVoter: ~ AppBundle\EventListener\CheckRequirementsSubscriber: ~ ``` If I'm registering a service with a class that implements `VoterInterface`, when would I ever *not* want that to be tagged with `security.voter`? Here's the proposed code: ```yml # proposed services: _defaults: autowire: true autoconfigure: true # services using the auto_configure_instanceof functionality AppBundle\Security\PostVoter: ~ AppBundle\EventListener\CheckRequirementsSubscriber: ~ ``` The user must opt into this and it only applies locally to this configuration file. It works because each enabled bundle would have the opportunity to add one or more "automatic instanceof" definitions - e.g. SecurityBundle would add the `security.voter` instanceof config, FrameworkBundle would add the `kernel.event_subscriber` instanceof config, etc. For another example, you can check out the proposed changes to `symfony-demo` - symfony/demo#483 - the `_instanceof` section is pretty heavy: https://github.com/hhamon/symfony-demo/blob/81694ac21e63bebe7ecfa22fa689766f2f38ccd5/app/config/services.yml#L20 Thanks! Commits ------- 18627bf9f6 [DI] Introducing autoconfigure: automatic _instanceof configuration
2 parents 0672c05 + 05ad3ef commit 8f47316

18 files changed

+290
-5
lines changed

Compiler/ResolveDefinitionTemplatesPass.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ private function doResolveDefinition(ChildDefinition $definition)
101101
$def->setPublic($parentDef->isPublic());
102102
$def->setLazy($parentDef->isLazy());
103103
$def->setAutowired($parentDef->isAutowired());
104+
$def->setAutoconfigured($parentDef->isAutoconfigured());
104105
$def->setChanges($parentDef->getChanges());
105106

106107
// overwrite with values specified in the decorator
@@ -129,6 +130,9 @@ private function doResolveDefinition(ChildDefinition $definition)
129130
if (isset($changes['autowired'])) {
130131
$def->setAutowired($definition->isAutowired());
131132
}
133+
if (isset($changes['autoconfigured'])) {
134+
$def->setAutoconfigured($definition->isAutoconfigured());
135+
}
132136
if (isset($changes['shared'])) {
133137
$def->setShared($definition->isShared());
134138
}

Compiler/ResolveInstanceofConditionalsPass.php

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,37 @@ public function process(ContainerBuilder $container)
3838

3939
private function processDefinition(ContainerBuilder $container, $id, Definition $definition)
4040
{
41-
if (!$instanceofConditionals = $definition->getInstanceofConditionals()) {
41+
$instanceofConditionals = $definition->getInstanceofConditionals();
42+
$automaticInstanceofConditionals = $definition->isAutoconfigured() ? $container->getAutomaticInstanceofDefinitions() : array();
43+
44+
if (!$instanceofConditionals && !$automaticInstanceofConditionals) {
4245
return $definition;
4346
}
47+
4448
if (!$class = $container->getParameterBag()->resolveValue($definition->getClass())) {
4549
return $definition;
4650
}
4751

52+
$conditionals = $this->mergeConditionals($automaticInstanceofConditionals, $instanceofConditionals);
53+
4854
$definition->setInstanceofConditionals(array());
4955
$parent = $shared = null;
5056
$instanceofTags = array();
5157

52-
foreach ($instanceofConditionals as $interface => $instanceofDef) {
58+
foreach ($conditionals as $interface => $instanceofDefs) {
5359
if ($interface !== $class && (!$container->getReflectionClass($interface) || !$container->getReflectionClass($class))) {
5460
continue;
5561
}
56-
if ($interface === $class || is_subclass_of($class, $interface)) {
62+
63+
if ($interface !== $class && !is_subclass_of($class, $interface)) {
64+
continue;
65+
}
66+
67+
foreach ($instanceofDefs as $key => $instanceofDef) {
68+
/** @var ChildDefinition $instanceofDef */
5769
$instanceofDef = clone $instanceofDef;
5870
$instanceofDef->setAbstract(true)->setInheritTags(false)->setParent($parent ?: 'abstract.instanceof.'.$id);
59-
$parent = 'instanceof.'.$interface.'.'.$id;
71+
$parent = 'instanceof.'.$interface.'.'.$key.'.'.$id;
6072
$container->setDefinition($parent, $instanceofDef);
6173
$instanceofTags[] = $instanceofDef->getTags();
6274
$instanceofDef->setTags(array());
@@ -100,4 +112,20 @@ private function processDefinition(ContainerBuilder $container, $id, Definition
100112

101113
return $definition;
102114
}
115+
116+
private function mergeConditionals(array $automaticInstanceofConditionals, array $instanceofConditionals)
117+
{
118+
// make each value an array of ChildDefinition
119+
$conditionals = array_map(function($childDef) { return array($childDef); }, $automaticInstanceofConditionals);
120+
121+
foreach ($instanceofConditionals as $interface => $instanceofDef) {
122+
if (!isset($automaticInstanceofConditionals[$interface])) {
123+
$conditionals[$interface] = array();
124+
}
125+
126+
$conditionals[$interface][] = $instanceofDef;
127+
}
128+
129+
return $conditionals;
130+
}
103131
}

ContainerBuilder.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
118118
*/
119119
private $vendors;
120120

121+
private $automaticInstanceofDefinitions = array();
122+
121123
public function __construct(ParameterBagInterface $parameterBag = null)
122124
{
123125
parent::__construct($parameterBag);
@@ -638,6 +640,14 @@ public function merge(ContainerBuilder $container)
638640
$this->envCounters[$env] += $count;
639641
}
640642
}
643+
644+
foreach ($container->getAutomaticInstanceofDefinitions() as $interface => $childDefinition) {
645+
if (isset($this->automaticInstanceofDefinitions[$interface])) {
646+
throw new InvalidArgumentException(sprintf('%s has already been autoconfigured and merge() does not support merging autoconfiguration for the same class/interface.', $interface));
647+
}
648+
649+
$this->automaticInstanceofDefinitions[$interface] = $childDefinition;
650+
}
641651
}
642652

643653
/**
@@ -1259,6 +1269,31 @@ public function getExpressionLanguageProviders()
12591269
return $this->expressionLanguageProviders;
12601270
}
12611271

1272+
/**
1273+
* Returns a ChildDefinition that will be used for autoconfiguring the interface/class.
1274+
*
1275+
* @param string $interface The class or interface to match
1276+
* @return ChildDefinition
1277+
*/
1278+
public function registerForAutoconfiguration($interface)
1279+
{
1280+
if (!isset($this->automaticInstanceofDefinitions[$interface])) {
1281+
$this->automaticInstanceofDefinitions[$interface] = new ChildDefinition('');
1282+
}
1283+
1284+
return $this->automaticInstanceofDefinitions[$interface];
1285+
}
1286+
1287+
/**
1288+
* Returns an array of ChildDefinition[] keyed by interface.
1289+
*
1290+
* @return ChildDefinition[]
1291+
*/
1292+
public function getAutomaticInstanceofDefinitions()
1293+
{
1294+
return $this->automaticInstanceofDefinitions;
1295+
}
1296+
12621297
/**
12631298
* Resolves env parameter placeholders in a string or an array.
12641299
*

Definition.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class Definition
3030
private $properties = array();
3131
private $calls = array();
3232
private $instanceof = array();
33+
private $autoconfigured = false;
3334
private $configurator;
3435
private $tags = array();
3536
private $public = true;
@@ -388,6 +389,30 @@ public function getInstanceofConditionals()
388389
return $this->instanceof;
389390
}
390391

392+
/**
393+
* Sets whether or not instanceof conditionals should be prepended with a global set.
394+
*
395+
* @param bool $autoconfigured
396+
*
397+
* @return $this
398+
*/
399+
public function setAutoconfigured($autoconfigured)
400+
{
401+
$this->changes['autoconfigured'] = true;
402+
403+
$this->autoconfigured = $autoconfigured;
404+
405+
return $this;
406+
}
407+
408+
/**
409+
* @return bool
410+
*/
411+
public function isAutoconfigured()
412+
{
413+
return $this->autoconfigured;
414+
}
415+
391416
/**
392417
* Sets tags for this definition.
393418
*

Dumper/XmlDumper.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ private function addService($definition, $id, \DOMElement $parent)
205205
$service->appendChild($autowiringType);
206206
}
207207

208+
if ($definition->isAutoconfigured()) {
209+
$service->setAttribute('autoconfigure', 'true');
210+
}
211+
208212
if ($callable = $definition->getConfigurator()) {
209213
$configurator = $this->document->createElement('configurator');
210214

Loader/XmlFileLoader.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ private function getServiceDefaults(\DOMDocument $xml, $file)
180180
if ($defaultsNode->hasAttribute('inherit-tags')) {
181181
$defaults['inherit-tags'] = XmlUtils::phpize($defaultsNode->getAttribute('inherit-tags'));
182182
}
183+
if ($defaultsNode->hasAttribute('autoconfigure')) {
184+
$defaults['autoconfigure'] = XmlUtils::phpize($defaultsNode->getAttribute('autoconfigure'));
185+
}
183186

184187
return $defaults;
185188
}
@@ -229,6 +232,9 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults =
229232
if (isset($defaults['autowire'])) {
230233
$definition->setAutowired($defaults['autowire']);
231234
}
235+
if (isset($defaults['autoconfigure'])) {
236+
$definition->setAutoconfigured($defaults['autoconfigure']);
237+
}
232238

233239
$definition->setChanges(array());
234240
}
@@ -248,6 +254,10 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults =
248254
$definition->setAutowired(XmlUtils::phpize($value));
249255
}
250256

257+
if ($value = $service->getAttribute('autoconfigure')) {
258+
$definition->setAutoconfigured(XmlUtils::phpize($value));
259+
}
260+
251261
if ($files = $this->getChildren($service, 'file')) {
252262
$definition->setFile($files[0]->nodeValue);
253263
}

Loader/YamlFileLoader.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class YamlFileLoader extends FileLoader
5757
'decoration_priority' => 'decoration_priority',
5858
'autowire' => 'autowire',
5959
'autowiring_types' => 'autowiring_types',
60+
'autoconfigure' => 'autoconfigure',
6061
);
6162

6263
private static $prototypeKeywords = array(
@@ -75,6 +76,7 @@ class YamlFileLoader extends FileLoader
7576
'tags' => 'tags',
7677
'inherit_tags' => 'inherit_tags',
7778
'autowire' => 'autowire',
79+
'autoconfigure' => 'autoconfigure',
7880
);
7981

8082
private static $instanceofKeywords = array(
@@ -86,13 +88,15 @@ class YamlFileLoader extends FileLoader
8688
'calls' => 'calls',
8789
'tags' => 'tags',
8890
'autowire' => 'autowire',
91+
'autoconfigure' => 'autoconfigure',
8992
);
9093

9194
private static $defaultsKeywords = array(
9295
'public' => 'public',
9396
'tags' => 'tags',
9497
'inherit_tags' => 'inherit_tags',
9598
'autowire' => 'autowire',
99+
'autoconfigure' => 'autoconfigure',
96100
);
97101

98102
private $yamlParser;
@@ -369,6 +373,9 @@ private function parseDefinition($id, $service, $file, array $defaults)
369373
if (isset($defaults['autowire'])) {
370374
$definition->setAutowired($defaults['autowire']);
371375
}
376+
if (isset($defaults['autoconfigure'])) {
377+
$definition->setAutoconfigured($defaults['autoconfigure']);
378+
}
372379

373380
$definition->setChanges(array());
374381
}
@@ -510,6 +517,10 @@ private function parseDefinition($id, $service, $file, array $defaults)
510517
}
511518
}
512519

520+
if (isset($service['autoconfigure'])) {
521+
$definition->setAutoconfigured($service['autoconfigure']);
522+
}
523+
513524
if (array_key_exists('resource', $service)) {
514525
if (!is_string($service['resource'])) {
515526
throw new InvalidArgumentException(sprintf('A "resource" attribute must be of type string for service "%s" in %s. Check your YAML syntax.', $id, $file));

Loader/schema/dic/services/services-1.0.xsd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
<xsd:attribute name="public" type="boolean" />
105105
<xsd:attribute name="autowire" type="boolean" />
106106
<xsd:attribute name="inherit-tags" type="boolean" />
107+
<xsd:attribute name="autoconfigure" type="boolean" />
107108
</xsd:complexType>
108109

109110
<xsd:complexType name="service">
@@ -132,6 +133,7 @@
132133
<xsd:attribute name="decoration-priority" type="xsd:integer" />
133134
<xsd:attribute name="autowire" type="boolean" />
134135
<xsd:attribute name="inherit-tags" type="boolean" />
136+
<xsd:attribute name="autoconfigure" type="boolean" />
135137
</xsd:complexType>
136138

137139
<xsd:complexType name="instanceof">
@@ -146,6 +148,7 @@
146148
<xsd:attribute name="public" type="boolean" />
147149
<xsd:attribute name="lazy" type="boolean" />
148150
<xsd:attribute name="autowire" type="boolean" />
151+
<xsd:attribute name="autoconfigure" type="boolean" />
149152
</xsd:complexType>
150153

151154
<xsd:complexType name="prototype">
@@ -167,6 +170,7 @@
167170
<xsd:attribute name="parent" type="xsd:string" />
168171
<xsd:attribute name="autowire" type="boolean" />
169172
<xsd:attribute name="inherit-tags" type="boolean" />
173+
<xsd:attribute name="autoconfigure" type="boolean" />
170174
</xsd:complexType>
171175

172176
<xsd:complexType name="tag">

Tests/Compiler/IntegrationTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ public function testInstanceofDefaultsAndParentDefinitionResolution()
130130
// instanceof overrides defaults
131131
$simpleService = $container->getDefinition('service_simple');
132132
$this->assertFalse($simpleService->isAutowired());
133+
$this->assertFalse($simpleService->isAutoconfigured());
133134
$this->assertFalse($simpleService->isShared());
134135

135136
// all tags are kept
@@ -156,6 +157,7 @@ public function testInstanceofDefaultsAndParentDefinitionResolution()
156157
// service override instanceof
157158
$overrideService = $container->getDefinition('service_override_instanceof');
158159
$this->assertTrue($overrideService->isAutowired());
160+
$this->assertTrue($overrideService->isAutoconfigured());
159161

160162
// children definitions get no instanceof
161163
$childDef = $container->getDefinition('child_service');

Tests/Compiler/ResolveDefinitionTemplatesPassTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,38 @@ public function testProcessSetsArguments()
381381
$this->assertSame(array(2, 1, 'foo' => 3), $def->getArguments());
382382
}
383383

384+
public function testSetAutoconfiguredOnServiceHasParent()
385+
{
386+
$container = new ContainerBuilder();
387+
388+
$container->register('parent', 'stdClass')
389+
->setAutoconfigured(true)
390+
;
391+
392+
$container->setDefinition('child1', new ChildDefinition('parent'))
393+
->setAutoconfigured(false)
394+
;
395+
396+
$this->process($container);
397+
398+
$this->assertFalse($container->getDefinition('child1')->isAutoconfigured());
399+
}
400+
401+
public function testSetAutoconfiguredOnServiceIsParent()
402+
{
403+
$container = new ContainerBuilder();
404+
405+
$container->register('parent', 'stdClass')
406+
->setAutoconfigured(true)
407+
;
408+
409+
$container->setDefinition('child1', new ChildDefinition('parent'));
410+
411+
$this->process($container);
412+
413+
$this->assertTrue($container->getDefinition('child1')->isAutoconfigured());
414+
}
415+
384416
protected function process(ContainerBuilder $container)
385417
{
386418
$pass = new ResolveDefinitionTemplatesPass();

0 commit comments

Comments
 (0)