Skip to content

Commit 05ad3ef

Browse files
weaverryanfabpot
authored andcommitted
[DI] Introducing autoconfigure: automatic _instanceof configuration
1 parent df7d2b7 commit 05ad3ef

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)