Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 94d6da9

Browse filesBrowse files
committed
Forbid having unused bindings
1 parent eba2aa8 commit 94d6da9
Copy full SHA for 94d6da9

17 files changed

+219
-29
lines changed
+47Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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;
13+
14+
/**
15+
* @author Guilhem Niot <guilhem.niot@gmail.com>
16+
*/
17+
final class Binding
18+
{
19+
private static $count;
20+
21+
private $identifier;
22+
private $value;
23+
24+
public function __construct($value)
25+
{
26+
$this->value = $value;
27+
$this->identifier = ++self::$count;
28+
}
29+
30+
public function getValue()
31+
{
32+
return $this->value;
33+
}
34+
35+
public function setValue($value)
36+
{
37+
$this->value = $value;
38+
}
39+
40+
/**
41+
* @internal
42+
*/
43+
public function getIdentifier()
44+
{
45+
return $this->identifier;
46+
}
47+
}

‎src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

1414
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
15+
use Symfony\Component\DependencyInjection\Binding;
1516
use Symfony\Component\DependencyInjection\Definition;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
1718

@@ -70,6 +71,8 @@ protected function processValue($value, $isRoot = false)
7071
if ($v = $value->getConfigurator()) {
7172
$value->setConfigurator($this->processValue($v));
7273
}
74+
} elseif ($value instanceof Binding) {
75+
$value->setValue($this->processValue($value->getValue()));
7376
}
7477

7578
return $value;

‎src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php
+38-8Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,34 @@
1414
use Symfony\Component\DependencyInjection\Definition;
1515
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1616
use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
1718
use Symfony\Component\DependencyInjection\Reference;
1819

1920
/**
2021
* @author Guilhem Niot <guilhem.niot@gmail.com>
2122
*/
2223
class ResolveBindingsPass extends AbstractRecursivePass
2324
{
25+
private $usedBindings = array();
26+
private $unusedBindings = array();
27+
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
public function process(ContainerBuilder $container)
32+
{
33+
try {
34+
parent::process($container);
35+
36+
foreach ($this->unusedBindings as list($key, $serviceId)) {
37+
throw new InvalidArgumentException(sprintf('Unused binding "%s" in service "%s".', $key, $serviceId));
38+
}
39+
} finally {
40+
$this->usedBindings = array();
41+
$this->unusedBindings = array();
42+
}
43+
}
44+
2445
/**
2546
* {@inheritdoc}
2647
*/
@@ -31,16 +52,17 @@ protected function processValue($value, $isRoot = false)
3152
}
3253

3354
foreach ($bindings as $key => $binding) {
34-
if (isset($key[0]) && '$' === $key[0]) {
35-
continue;
55+
if (!isset($this->usedBindings[$binding->getIdentifier()])) {
56+
$this->unusedBindings[$binding->getIdentifier()] = array($key, $this->currentId);
3657
}
3758

38-
if (null !== $binding && !$binding instanceof Reference && !$binding instanceof Definition) {
39-
throw new InvalidArgumentException(sprintf('Invalid value for binding key "%s" for service "%s": expected null, an instance of %s or an instance of %s, %s given.', $key, $this->currentId, Reference::class, Definition::class, gettype($binding)));
59+
if (isset($key[0]) && '$' === $key[0]) {
60+
continue;
4061
}
4162

42-
if (!class_exists($key) && !interface_exists($key, false)) {
43-
throw new InvalidArgumentException(sprintf('Invalid binding key "%s" for service "%s": this class or interface does not exist.', $key, $this->currentId));
63+
$bindingValue = $binding->getValue();
64+
if (null !== $bindingValue && !$bindingValue instanceof Reference && !$bindingValue instanceof Definition) {
65+
throw new InvalidArgumentException(sprintf('Invalid value for binding key "%s" for service "%s": expected null, an instance of %s or an instance of %s, %s given.', $key, $this->currentId, Reference::class, Definition::class, gettype($bindingValue)));
4466
}
4567
}
4668

@@ -64,7 +86,11 @@ protected function processValue($value, $isRoot = false)
6486
}
6587

6688
if (array_key_exists('$'.$parameter->name, $bindings)) {
67-
$arguments[$key] = $bindings['$'.$parameter->name];
89+
$binding = $bindings['$'.$parameter->name];
90+
$arguments[$key] = $binding->getValue();
91+
92+
$this->usedBindings[$binding->getIdentifier()] = true;
93+
unset($this->unusedBindings[$binding->getIdentifier()]);
6894

6995
continue;
7096
}
@@ -75,7 +101,11 @@ protected function processValue($value, $isRoot = false)
75101
continue;
76102
}
77103

78-
$arguments[$key] = $bindings[$typeHint];
104+
$binding = $bindings[$typeHint];
105+
$arguments[$key] = $bindings[$typeHint]->getValue();
106+
107+
$this->usedBindings[$binding->getIdentifier()] = true;
108+
unset($this->unusedBindings[$binding->getIdentifier()]);
79109
}
80110

81111
if ($arguments !== $call[1]) {

‎src/Symfony/Component/DependencyInjection/Compiler/ResolveNamedArgumentsPass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/ResolveNamedArgumentsPass.php
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\DependencyInjection\Definition;
1515
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1616
use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
17+
use Symfony\Component\DependencyInjection\Reference;
1718

1819
/**
1920
* Resolves named arguments to their corresponding numeric index.
@@ -75,7 +76,7 @@ protected function processValue($value, $isRoot = false)
7576
}
7677

7778
if (null !== $argument && !$argument instanceof Reference && !$argument instanceof Definition) {
78-
throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": the value of argument "%s" of method "%s::%s" must be null, an instance of %s or an instance of %s, %s given.', $key, $this->currentId, $key, $class, $method, Reference::class, Definition::class, gettype($argument)));
79+
throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": the value of argument "%s" of method "%s::%s" must be null, an instance of %s or an instance of %s, %s given.', $this->currentId, $key, $class, $method, Reference::class, Definition::class, gettype($argument)));
7980
}
8081

8182
foreach ($parameters as $j => $p) {

‎src/Symfony/Component/DependencyInjection/Definition.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Definition.php
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,12 @@ public function getBindings()
824824
*/
825825
public function setBindings(array $bindings)
826826
{
827+
foreach ($bindings as $key => $binding) {
828+
if (!$binding instanceof Binding) {
829+
$bindings[$key] = new Binding($binding);
830+
}
831+
}
832+
827833
$this->bindings = $bindings;
828834

829835
return $this;

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/FileLoader.php
-1Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ public function registerClasses(Definition $prototype, $namespace, $resource)
5959
}
6060

6161
$classes = $this->findClasses($namespace, $resource);
62-
// prepare for deep cloning
6362
$prototype = serialize($prototype);
6463

6564
foreach ($classes as $class) {

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Symfony\Component\DependencyInjection\Reference;
2222
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
2323
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
24+
use Symfony\Component\DependencyInjection\Binding;
2425
use Symfony\Component\ExpressionLanguage\Expression;
2526

2627
/**
@@ -164,7 +165,9 @@ private function getServiceDefaults(\DOMDocument $xml, $file)
164165
}
165166
$defaults = array(
166167
'tags' => $this->getChildren($defaultsNode, 'tag'),
167-
'bindings' => $this->getArgumentsAsPhp($defaultsNode, 'binding'),
168+
'bindings' => array_map(function ($v) {
169+
return new Binding($v);
170+
}, $this->getArgumentsAsPhp($defaultsNode, 'binding')),
168171
);
169172

170173
foreach ($defaults['tags'] as $tag) {

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Symfony\Component\DependencyInjection\Reference;
2222
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
2323
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
24+
use Symfony\Component\DependencyInjection\Binding;
2425
use Symfony\Component\Yaml\Exception\ParseException;
2526
use Symfony\Component\Yaml\Parser as YamlParser;
2627
use Symfony\Component\Yaml\Tag\TaggedValue;
@@ -295,7 +296,9 @@ private function parseDefaults(array &$content, $file)
295296
throw new InvalidArgumentException(sprintf('Parameter "bindings" in "_defaults" must be an array in %s. Check your YAML syntax.', $file));
296297
}
297298

298-
$defaults['bindings'] = $this->resolveServices($defaults['bindings'], $file);
299+
$defaults['bindings'] = array_map(function ($v) {
300+
return new Binding($v);
301+
}, $this->resolveServices($defaults['bindings'], $file));
299302
}
300303

301304
return $defaults;
+59Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Tests\Compiler;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy;
19+
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
20+
use Symfony\Component\DependencyInjection\Binding;
21+
22+
class ResolveBindingsPassTest extends TestCase
23+
{
24+
public function testProcess()
25+
{
26+
$container = new ContainerBuilder();
27+
28+
$bindings = array(CaseSensitiveClass::class => new Binding(new Reference('foo')));
29+
30+
$definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class);
31+
$definition->setArguments(array(1 => '123'));
32+
$definition->addMethodCall('setSensitiveClass');
33+
$definition->setBindings($bindings);
34+
35+
$container->register('foo', CaseSensitiveClass::class)
36+
->setBindings($bindings);
37+
38+
$pass = new ResolveBindingsPass();
39+
$pass->process($container);
40+
41+
$this->assertEquals(array(new Reference('foo'), '123'), $definition->getArguments());
42+
$this->assertEquals(array(array('setSensitiveClass', array(new Reference('foo')))), $definition->getMethodCalls());
43+
}
44+
45+
/**
46+
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
47+
* @expectedExceptionMessage Unused binding "$quz" in service "Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy".
48+
*/
49+
public function testUnusedBinding()
50+
{
51+
$container = new ContainerBuilder();
52+
53+
$definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class);
54+
$definition->setBindings(array('$quz' => '123'));
55+
56+
$pass = new ResolveBindingsPass();
57+
$pass->process($container);
58+
}
59+
}

‎src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveNamedArgumentsPassTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveNamedArgumentsPassTest.php
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
1717
use Symfony\Component\DependencyInjection\Reference;
1818
use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy;
19+
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
1920

2021
/**
2122
* @author Kévin Dunglas <dunglas@gmail.com>
@@ -92,6 +93,19 @@ public function testArgumentNotFound()
9293
$pass = new ResolveNamedArgumentsPass();
9394
$pass->process($container);
9495
}
96+
97+
public function testTypedArgument()
98+
{
99+
$container = new ContainerBuilder();
100+
101+
$definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class);
102+
$definition->setArguments(array('$apiKey' => '123', CaseSensitiveClass::class => new Reference('foo')));
103+
104+
$pass = new ResolveNamedArgumentsPass();
105+
$pass->process($container);
106+
107+
$this->assertEquals(array(new Reference('foo'), '123'), $definition->getArguments());
108+
}
95109
}
96110

97111
class NoConstructor

‎src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/Bar.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
class Bar implements BarInterface
1515
{
16-
public function __construct(BarInterface $decorated = null, array $foo = array())
16+
public function __construct($quz = null, \NonExistent $nonExistent = null, BarInterface $decorated = null, array $foo = array(), $fromParent = null)
1717
{
1818
}
1919
}

‎src/Symfony/Component/DependencyInjection/Tests/Fixtures/CaseSensitiveClass.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/CaseSensitiveClass.php
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,7 @@
1313

1414
class CaseSensitiveClass
1515
{
16+
public function __construct()
17+
{
18+
}
1619
}

‎src/Symfony/Component/DependencyInjection/Tests/Fixtures/NamedArgumentsDummy.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/NamedArgumentsDummy.php
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,8 @@ public function __construct(CaseSensitiveClass $c, $apiKey)
1414
public function setApiKey($apiKey)
1515
{
1616
}
17+
18+
public function setSensitiveClass(CaseSensitiveClass $c)
19+
{
20+
}
1721
}

‎src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_bindings.xml

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services_bindings.xml
+4-3Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
</instanceof>
77

88
<defaults>
9-
<binding key="Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy">null</binding>
9+
<binding key="NonExistent">null</binding>
10+
<binding key="$quz">null</binding>
1011
</defaults>
1112

1213
<service id="bar" class="Symfony\Component\DependencyInjection\Tests\Fixtures\Bar" parent="parent" autowire="true">
@@ -17,8 +18,8 @@
1718
</service>
1819

1920
<service id="parent" abstract="true">
20-
<binding key="$foo">notinjected</binding>
21-
<binding key="$bar">bar</binding>
21+
<binding key="$foo">overriden</binding>
22+
<binding key="$fromParent">fromParent</binding>
2223
</service>
2324

2425
<service id="Symfony\Component\DependencyInjection\Tests\Fixtures\Bar" />

‎src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_bindings.yml

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_bindings.yml
+4-3Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ services:
66

77
_defaults:
88
bindings:
9-
Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy: ~
9+
NonExistent: ~
10+
$quz: ~
1011

1112
bar:
1213
class: Symfony\Component\DependencyInjection\Tests\Fixtures\Bar
@@ -19,7 +20,7 @@ services:
1920
parent:
2021
abstract: true
2122
bindings:
22-
$foo: notinjected
23-
$bar: bar
23+
$foo: overriden
24+
$fromParent: fromParent
2425

2526
Symfony\Component\DependencyInjection\Tests\Fixtures\Bar: ~

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.