8000 [DependencyInjection] Add "instanceof" section for local interface-de… · symfony/symfony@580f2bd · GitHub
[go: up one dir, main page]

Skip to content

Commit 580f2bd

Browse files
[DependencyInjection] Add "instanceof" section for local interface-defined configs
1 parent ed6a2ed commit 580f2bd

File tree

7 files changed

+144
-11
lines changed

7 files changed

+144
-11
lines changed

src/Symfony/Component/DependencyInjection/ChildDefinition.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class ChildDefinition extends Definition
2626
private $changes = array();
2727

2828
/**
29-
* @param string $parent The id of Definition instance to decorate
29+
* @param string|null $parent The id of Definition instance to decorate
3030
*/
3131
public function __construct($parent)
3232
{

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,27 @@ protected function processValue($value, $isRoot = false) 8000
4141
$this->container->setDefinition($this->currentId, $value);
4242
}
4343
}
44+
$parameterBag = $this->container->getParameterBag();
45+
$class = $parameterBag->resolveValue($value->getClass());
46+
if ($class && $instanceof = $value->getInstanceofConditionals()) {
47+
foreach ($instanceof as $interface => $definition) {
48+
$interface = $parameterBag->resolveValue($interface);
49+
if ($interface !== $class && (!$this->container->getReflectionClass($interface) || !$this->container->getReflectionClass($class))) {
50+
continue;
51+
}
52+
if ($interface === $class || is_subclass_of($class, $interface)) {
53+
$definition = clone $definition;
54+
$definition->setAbstract($value->isAbstract() || $definition->isAbstract());
55+
$definition->setShared($value->isShared(), $definition->isShared());
56+
$definition->setInheritTags(true);
57+
$definition->setAutowiredMethods(array_merge($value->getAutowiredMethods(), $definition->getAutowiredMethods()));
58+
$value = $this->mergeDefinition($value, $definition);
59+
}
60+
}
61+
if ($isRoot) {
62+
$this->container->setDefinition($this->currentId, $value);
63+
}
64+
}
4465

4566
return parent::processValue($value, $isRoot);
4667
}
@@ -81,6 +102,12 @@ private function doResolveDefinition(ChildDefinition $definition)
81102
}
82103

83104
$this->container->log($this, sprintf('Resolving inheritance for "%s" (parent: %s).', $this->currentId, $parent));
105+
106+
return $this->mergeDefinition($parentDef, $definition);
107+
}
108+
109+
private function mergeDefinition(Definition $parentDef, ChildDefinition $definition)
110+
{
84111
$def = new Definition();
85112

86113
// merge in parent definition

src/Symfony/Component/DependencyInjection/Definition.php

Lines changed: 20 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;
@@ -355,6 +356,24 @@ public function getOverriddenGetters()
355356
return $this->getters;
356357
}
357358

359+
/**
360+
* @experimental in version 3.3
361+
*/
362+
public function setInstanceofConditionals(array $instanceof)
363+
{
364+
$this->instanceof = $instanceof;
365+
366+
return $this;
367+
}
368+
369+
/**
370+
* @experimental in version 3.3
371+
*/
372+
public function getInstanceofConditionals()
373+
{
374+
return $this->instanceof;
375+
}
376+
358377
/**
359378
* Sets tags for this definition.
360379
*
@@ -728,9 +747,7 @@ public function getAutowiredMethods()
728747
*/
729748
public function setAutowired($autowired)
730749
{
731-
$this->autowiredMethods = $autowired ? array('__construct') : array();
732-
733-
return $this;
750+
return $this->setAutowiredMethods($autowired ? array('__construct') : array());
734751
}
735752

736753
/**

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Loader;
1313

14+
use Symfony\Component\DependencyInjection\ChildDefinition;
1415
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Definition;
1517
use Symfony\Component\Config\Loader\FileLoader as BaseFileLoader;
1618
use Symfony\Component\Config\FileLocatorInterface;
1719

@@ -23,6 +25,20 @@
2325
abstract class FileLoader extends BaseFileLoader
2426
{
2527
protected $container;
28+
protected $isLoadingInstanceof = false;
29+
protected $instanceof = array();
30+
protected $instanceofBlacklistedAttributes = array(
31+
'psr4' => 'psr4',
32+
'alias' => 'alias',
33+
'parent' => 'parent',
34+
'class' => 'class',
35+
'synthetic' => 'synthetic',
36+
'inherit_tags' => 'inherit_tags',
37+
'decorates' => 'decorates',
38+
'decoration_inner_name' => 'decoration_inner_name',
39+
'decoration_priority' => 'decoration_priority',
40+
'autowiring_types' => 'autowiring_types',
41+
);
2642

2743
/**
2844
* @param ContainerBuilder $container A ContainerBuilder instance
@@ -34,4 +50,19 @@ public function __construct(ContainerBuilder $container, FileLocatorInterface $l
3450

3551
parent::__construct($locator);
3652
}
53+
54+
/**
55+
* @experimental in version 3.3
56+
*/
57+
protected function setDefinition($id, Definition $definition)
58+
{
59+
if ($this->isLoadingInstanceof) {
60+
if (!$definition instanceof ChildDefinition) {
61+
throw new InvalidArgumentException(sprintf('Invalid type definition "%s": ChildDefinition expected, %s given.', $id, get_class($definition)));
62+
}
63+
$this->instanceof[$id] = $definition;
64+
} else {
65+
$this->container->setDefinition($id, $definition->setInstanceofConditionals($this->instanceof));
66+
}
67+
}
3768
}

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

Lines changed: 28 additions & 4 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
/**
@@ -125,10 +129,28 @@ private function parseDefinitions(\DOMDocument $xml, $file)
125129
return;
126130
}
127131

132+
$this->instanceof = array();
133+
$this->isLoadingInstanceof = true;
134+
$instanceof = $xpath->query('//container:services/container:instanceof/container:service');
135+
foreach ($instanceof as $service) {
136+
$id = (string) $service->getAttribute('id');
137+
foreach ($this->instanceofBlacklistedAttributes as $name) {
138+
$name = str_replace('_', '-', $name);
139+
if ($service->hasAttribute($name)) {
140+
throw new InvalidArgumentException(sprintf('Type definition "%s" cannot use attribute "%s" within the "<instanceof>" tag in %s.', $name, $file));
141+
}
142+
if ($this->getChildren($service, $name)) {
143+
throw new InvalidArgumentException(sprintf('Type definition "%s" cannot use "<%s>" tags within the "<instanceof>" tag in %s.', $name, $file));
144+
}
145+
}
146+
$this->setDefinition($id, $this->parseDefinition($service, $file, array()));
147+
}
148+
149+
$this->isLoadingInstanceof = false;
128150
$defaults = $this->getServiceDefaults($xml, $file);
129151
foreach ($services as $service) {
130152
if (null !== $definition = $this->parseDefinition($service, $file, $defaults)) {
131-
$this->container->setDefinition((string) $service->getAttribute('id'), $definition);
153+
$this->setDefinition((string) $service->getAttribute('id'), $definition);
132154
}
133155
}
134156
}
@@ -204,7 +226,9 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults =
204226
return;
205227
}
206228

207-
if ($parent = $service->getAttribute('parent')) {
229+
if ($this->isLoadingInstanceof) {
230+
$definition = new ChildDefinition(null);
231+
} elseif ($parent = $service->getAttribute('parent')) {
208232
$definition = new ChildDefinition($parent);
209233

210234
if ($value = $service->getAttribute('inherit-tags')) {
@@ -417,7 +441,7 @@ private function processAnonymousServices(\DOMDocument $xml, $file)
417441
uksort($definitions, 'strnatcmp');
418442
foreach (array_reverse($definitions) as $id => list($domElement, $file, $wild)) {
419443
if (null !== $definition = $this->parseDefinition($domElement, $file)) {
420-
$this->container->setDefinition($id, $definition);
444+
$this->setDefinition($id, $definition);
421445
}
422446

423447
if (true === $wild) {

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

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,11 @@ public function load($resource, $type = null)
9797
$this->loadFromExtensions($content);
9898

9999
// services
100-
$this->parseDefinitions($content, $resource);
100+
try {
101+
$this->parseDefinitions($content, $resource);
102+
} finally {
103+
$this->instanceof = array();
104+
}
101105
}
102106

103107
/**
@@ -159,6 +163,27 @@ private function parseDefinitions(array $content, $file)
159163
throw new InvalidArgumentException(sprintf('The "services" key should contain an array in %s. Check your YAML syntax.', $file));
160164
}
161165

166+
if ($this->isUnderscoredParamValid($content, '_instanceof', $file)) {
167+
$this->instanceof = array();
168+
$this->isLoadingInstanceof = true;
169+
foreach ($content['services']['_instanceof'] as $id => $service) {
170+
if (!$service || !is_array($service)) {
171+
throw new InvalidArgumentException(sprintf('Type definition "%s" must be a non-empty array within "_instanceof" in %s. Check your YAML syntax.', $id, $file));
172+
}
173+
if (is_string($service) && 0 === strpos($service, '@')) {
174+
throw new InvalidArgumentException(sprintf('Type definition "%s" cannot be an alias within "_instanceof" in %s. Check your YAML syntax.', $id, $file));
175+
}
176+
foreach ($this->instanceofBlacklistedAttributes as $name) {
177+
if (array_key_exists($name, $service)) {
178+
throw new InvalidArgumentException(sprintf('Type definition "%s" cannot use attribute "%s" within "_instanceof" in %s. Check your YAML syntax.', $name, $file));
179+
}
180+
}
181+
$this->parseDefinition($id, $service, $file, array());
182+
}
183+
unset($content['services']['_instanceof']);
184+
}
185+
186+
$this->isLoadingInstanceof = false;
162187
$defaults = $this->parseDefaults($content, $file);
163188
foreach ($content['services'] as $id => $service) {
164189
$this->parseDefinition($id, $service, $file, $defaults);
@@ -273,7 +298,9 @@ private function parseDefinition($id, $service, $file, array $defaults)
273298
return;
274299
}
275300

276-
if (isset($service['parent'])) {
301+
if ($this->isLoadingInstanceof) {
302+
$definition = new ChildDefinition(null);
303+
} elseif (isset($service['parent'])) {
277304
$definition = new ChildDefinition($service['parent']);
278305

279306
$inheritTag = isset($service['inherit_tags']) ? $service['inherit_tags'] : (isset($defaults['inherit_tags']) ? $defaults['inherit_tags'] : null);
@@ -432,7 +459,7 @@ private function parseDefinition($id, $service, $file, array $defaults)
432459
}
433460
}
434461

435-
$this->container->setDefinition($id, $definition);
462+
$this->setDefinition($id, $definition);
436463
}
437464

438465
/**

src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
<xsd:choice maxOccurs="unbounded">
5656
<xsd:element name="service" type="service" minOccurs="1" />
5757
<xsd:element name="defaults" type="defaults" minOccurs="0" maxOccurs="1" />
58+
<xsd:element name="instanceof" type="instanceof" minOccurs="0" maxOccurs="1" />
5859
</xsd:choice>
5960
</xsd:complexType>
6061

@@ -136,6 +137,12 @@
136137
<xsd:attribute name="inherit-tags" type="boolean" />
137138
</xsd:complexType>
138139

140+
<xsd:complexType name="instanceof">
141+
<xsd:choice maxOccurs="unbounded">
142+
<xsd:element name="service" type="service" minOccurs="1" />
143+
</xsd:choice>
144+
</xsd:complexType>
145+
139146
<xsd:complexType name="tag">
140147
<xsd:attribute name="name" type="xsd:string" use="required" />
141148
<xsd:anyAttribute namespace="##any" processContents="lax" />

0 commit comments

Comments
 (0)
0