Skip to content

Commit 2fb6019

Browse files
[DependencyInjection] Add "instanceof" section for local interface-defined configs
1 parent 0a3cd97 commit 2fb6019

File tree

10 files changed

+285
-47
lines changed

10 files changed

+285
-47
lines changed

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
3.3.0
55
-----
66

7+
* [EXPERIMENTAL] added "instanceof" section for local interface-defined configs
78
* [EXPERIMENTAL] added "service-locator" argument for lazy loading a set of identified values and services
89
* [EXPERIMENTAL] added prototype services for PSR4-based discovery and registration
910
* added `ContainerBuilder::getReflectionClass()` for retrieving and tracking reflection class info

src/Symfony/Component/DependencyInjection/ChildDefinition.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@ public function setFile($file)
119119
return parent::setFile($file);
120120
}
121121

122+
/**
123+
* {@inheritdoc}
124+
*/
125+
public function setShared($boolean)
126+
{
127+
$this->changes['shared'] = true;
128+
129+
return parent::setShared($boolean);
130+
}
131+
122132
/**
123133
* {@inheritdoc}
124134
*/
@@ -139,6 +149,16 @@ public function setLazy($boolean)
139149
return parent::setLazy($boolean);
140150
}
141151

152+
/**
153+
* {@inheritdoc}
154+
*/
155+
public function setAbstract($boolean)
156+
{
157+
$this->changes['abstract'] = true;
158+
159+
return parent::setAbstract($boolean);
160+
}
161+
142162
/**
143163
* {@inheritdoc}
144164
*/

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public function __construct()
4242
$this->beforeOptimizationPasses = array(
4343
100 => array(
4444
$resolveClassPass = new ResolveClassPass(),
45+
new ResolveDefinitionInheritancePass(),
4546
),
4647
);
4748

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\ChildDefinition;
15+
use Symfony\Component\DependencyInjection\Definition;
16+
17+
/**
18+
* Applies tags and instanceof inheritance to definitions.
19+
*
20+
* @author Nicolas Grekas <[email protected]>
21+
*/
22+
class ResolveDefinitionInheritancePass extends AbstractRecursivePass
23+
{
24+
protected function processValue($value, $isRoot = false)
25+
{
26+
if (!$value instanceof Definition) {
27+
return parent::processValue($value, $isRoot);
28+
}
29+
if ($value instanceof ChildDefinition) {
30+
$this->resolveDefinition($value);
31+
}
32+
$class = $value->getClass();
33+
if (!$class || false !== strpos($class, '%') || !$instanceof = $value->getInstanceofConditionals()) {
34+
return parent::processValue($value, $isRoot);
35+
}
36+
37+
foreach ($instanceof as $interface => $definition) {
38+
if ($interface !== $class && (!$this->container->getReflectionClass($interface) || !$this->container->getReflectionClass($class))) {
39+
continue;
40+
}
41+
if ($interface === $class || is_subclass_of($class, $interface)) {
42+
$this->mergeDefinition($value, $definition);
43+
}
44+
}
45+
46+
return parent::processValue($value, $isRoot);
47+
}
48+
49+
/**
50+
* Populates the class and tags from parent definitions.
51+
*/
52+
private function resolveDefinition(ChildDefinition $definition)
53+
{
54+
if (!$this->container->has($parent = $definition->getParent())) {
55+
return;
56+
}
57+
58+
$parentDef = $this->container->findDefinition($parent);
59+
if ($parentDef instanceof ChildDefinition) {
60+
$this->resolveDefinition($parentDef);
61+
}
62+
63+
if (!isset($definition->getChanges()['class'])) {
64+
$definition->setClass($parentDef->getClass());
65+
}
66+
67+
// append parent tags when inheriting is enabled
68+
if ($definition->getInheritTags()) {
69+
foreach ($parentDef->getTags() as $k => $v) {
70+
foreach ($v as $v) {
71+
$definition->addTag($k, $v);
72+
}
73+
}
74+
}
75+
76+
$definition->setInheritTags(false);
77+
}
78+
79+
private function mergeDefinition(Definition $def, ChildDefinition $definition)
80+
{
81+
$changes = $definition->getChanges();
82+
if (isset($changes['shared'])) {
83+
$def->setShared($definition->isShared());
84+
}
85+
if (isset($changes['abstract'])) {
86+
$def->setAbstract($definition->isAbstract());
87+
}
88+
if (isset($changes['autowired_calls'])) {
89+
$autowiredCalls = $def->getAutowiredCalls();
90+
}
91+
92+
ResolveDefinitionTemplatesPass::mergeDefinition($def, $definition);
93+
94+
// merge autowired calls
95+
if (isset($changes['autowired_calls'])) {
96+
$def->setAutowiredCalls(array_merge($autowiredCalls, $def->getAutowiredCalls()));
97+
}
98+
99+
// merge tags
100+
foreach ($definition->getTags() as $k => $v) {
101+
foreach ($v as $v) {
102+
$def->addTag($k, $v);
103+
}
104+
}
105+
}
106+
}

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

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,26 @@ private function doResolveDefinition(ChildDefinition $definition)
103103
$def->setLazy($parentDef->isLazy());
104104
$def->setAutowiredCalls($parentDef->getAutowiredCalls());
105105

106+
self::mergeDefinition($def, $definition);
107+
108+
// merge autowiring types
109+
foreach ($definition->getAutowiringTypes(false) as $autowiringType) {
110+
$def->addAutowiringType($autowiringType);
111+
}
112+
113+
// these attributes are always taken from the child
114+
$def->setAbstract($definition->isAbstract());
115+
$def->setShared($definition->isShared());
116+
$def->setTags($definition->getTags());
117+
118+
return $def;
119+
}
120+
121+
/**
122+
* @internal
123+
*/
124+
public static function mergeDefinition(Definition $def, ChildDefinition $definition)
125+
{
106126
// overwrite with values specified in the decorator
107127
$changes = $definition->getChanges();
108128
if (isset($changes['class'])) {
@@ -168,26 +188,5 @@ private function doResolveDefinition(ChildDefinition $definition)
168188
foreach ($definition->getOverriddenGetters() as $k => $v) {
169189
$def->setOverriddenGetter($k, $v);
170190
}
171-
172-
// merge autowiring types
173-
foreach ($definition->getAutowiringTypes(false) as $autowiringType) {
174-
$def->addAutowiringType($autowiringType);
175-
}
176-
177-
// these attributes are always taken from the child
178-
$def->setAbstract($definition->isAbstract());
179-
$def->setShared($definition->isShared());
180-
$def->setTags($definition->getTags());
181-
182-
// append parent tags when inheriting is enabled
183-
if ($definition->getInheritTags()) {
184-
foreach ($parentDef->getTags() as $k => $v) {
185-
foreach ($v as $v) {
186-
$def->addTag($k, $v);
187-
}
188-
}
189-
}
190-
191-
return $def;
192191
}
193192
}

src/Symfony/Component/DependencyInjection/Definition.php

Lines changed: 28 additions & 3 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 $getters = array();
33+
private $instanceof = array();
3334
private $configurator;
3435
private $tags = array();
3536
private $public = true;
@@ -363,6 +364,32 @@ public function getOverriddenGetters()
363364
return $this->getters;
364365
}
365366

367+
/**
368+
* Sets the definition templates to conditionally apply on the current definition, keyed by parent interface/class.
369+
*
370+
* @param $instanceof ChildDefinition[]
371+
*
372+
* @experimental in version 3.3
373+
*/
374+
public function setInstanceofConditionals(array $instanceof)
375+
{
376+
$this->instanceof = $instanceof;
377+
378+
return $this;
379+
}
380+
381+
/**
382+
* Gets the definition templates to conditionally apply on the current definition, keyed by parent interface/class.
383+
*
384+
* @return ChildDefinition[]
385+
*
386+
* @experimental in version 3.3
387+
*/
388+
public function getInstanceofConditionals()
389+
{
390+
return $this->instanceof;
391+
}
392+
366393
/**
367394
* Sets tags for this definition.
368395
*
@@ -736,9 +763,7 @@ public function getAutowiredCalls()
736763
*/
737764
public function setAutowired($autowired)
738765
{
739-
$this->autowiredCalls = $autowired ? array('__construct') : array();
740-
741-
return $this;
766+
return $this->setAutowiredCalls($autowired ? array('__construct') : array());
742767
}
743768

744769
/**

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Loader;
1313

1414
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
15+
use Symfony\Component\DependencyInjection\ChildDefinition;
1516
use Symfony\Component\DependencyInjection\ContainerBuilder;
1617
use Symfony\Component\DependencyInjection\Definition;
1718
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@@ -29,6 +30,8 @@
2930
abstract class FileLoader extends BaseFileLoader
3031
{
3132
protected $container;
33+
protected $isLoadingInstanceof = false;
34+
protected $instanceof = array();
3235

3336
/**
3437
* @param ContainerBuilder $container A ContainerBuilder instance
@@ -80,7 +83,22 @@ public function registerClasses(Definition $prototype, $namespace, $resource)
8083
$prototype = serialize($prototype);
8184

8285
foreach ($classes as $class) {
83-
$this->container->setDefinition($class, unserialize($prototype));
86+
$this->setDefinition($class, unserialize($prototype));
87+
}
88+
}
89+
90+
/**
91+
* @experimental in version 3.3
92+
*/
93+
protected function setDefinition($id, Definition $definition)
94+
{
95+
if ($this->isLoadingInstanceof) {
96+
if (!$definition instanceof ChildDefinition) {
97+
throw new InvalidArgumentException(sprintf('Invalid type definition "%s": ChildDefinition expected, "%s" given.', $id, get_class($definition)));
98+
}
99+
$this->instanceof[$id] = $definition;
100+
} else {
101+
$this->container->setDefinition($id, $definition->setInstanceofConditionals($this->instanceof));
84102
}
85103
}
86104

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,11 @@ public function load($resource, $type = null)
5757
$this->loadFromExtensions($xml);
5858

5959
// services
60-
$this->parseDefinitions($xml, $path);
60+
try {
61+
$this->parseDefinitions($xml, $path);
62+
} finally {
63+
$this->instanceof = array();
64+
}
6165
}
6266

6367
/**
@@ -126,13 +130,21 @@ private function parseDefinitions(\DOMDocument $xml, $file)
126130
}
127131
$this->setCurrentDir(dirname($file));
128132

133+
$this->instanceof = array();
134+
$this->isLoadingInstanceof = true;
135+
$instanceof = $xpath->query('//container:services/container:instanceof');
136+
foreach ($instanceof as $service) {
137+
$this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, array()));
138+
}
139+
140+
$this->isLoadingInstanceof = false;
129141
$defaults = $this->getServiceDefaults($xml, $file);
130142
foreach ($services as $service) {
131143
if (null !== $definition = $this->parseDefinition($service, $file, $defaults)) {
132144
if ('prototype' === $service->tagName) {
133145
$this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'));
134146
} else {
135-
$this->container->setDefinition((string) $service->getAttribute('id'), $definition);
147+
$this->setDefinition((string) $service->getAttribute('id'), $definition);
136148
}
137149
}
138150
}
@@ -209,7 +221,9 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults =
209221
return;
210222
}
211223

212-
if ($parent = $service->getAttribute('parent')) {
224+
if ($this->isLoadingInstanceof) {
225+
$definition = new ChildDefinition('');
226+
} elseif ($parent = $service->getAttribute('parent')) {
213227
$definition = new ChildDefinition($parent);
214228

215229
if ($value = $service->getAttribute('inherit-tags')) {
@@ -247,7 +261,7 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults =
247261
$definition->setDeprecated(true, $deprecated[0]->nodeValue ?: null);
248262
}
249263

250-
$definition->setArguments($this->getArgumentsAsPhp($service, 'argument', false, (bool) $parent));
264+
$definition->setArguments($this->getArgumentsAsPhp($service, 'argument', false, $definition instanceof ChildDefinition));
251265
$definition->setProperties($this->getArgumentsAsPhp($service, 'property'));
252266
$definition->setOverriddenGetters($this->getArgumentsAsPhp($service, 'getter'));
253267

@@ -422,7 +436,7 @@ private function processAnonymousServices(\DOMDocument $xml, $file)
422436
uksort($definitions, 'strnatcmp');
423437
foreach (array_reverse($definitions) as $id => list($domElement, $file, $wild)) {
424438
if (null !== $definition = $this->parseDefinition($domElement, $file)) {
425-
$this->container->setDefinition($id, $definition);
439+
$this->setDefinition($id, $definition);
426440
}
427441

428442
if (true === $wild) {

0 commit comments

Comments
 (0)