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 6e50129

Browse filesBrowse files
committed
[DependencyInjection] Add support for named arguments
1 parent 91904af commit 6e50129
Copy full SHA for 6e50129

File tree

Expand file treeCollapse file tree

10 files changed

+308
-7
lines changed
Filter options
Expand file treeCollapse file tree

10 files changed

+308
-7
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public function __construct()
5353
new ResolveFactoryClassPass(),
5454
new FactoryReturnTypePass($resolveClassPass),
5555
new CheckDefinitionValidityPass(),
56+
new ResolveNamedArgumentsPass(),
5657
new AutowirePass(),
5758
new ResolveReferencesToAliasesPass(),
5859
new ResolveInvalidReferencesPass(),
+113Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\Definition;
15+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
16+
17+
/**
18+
* Resolves named arguments to their corresponding numeric index.
19+
*
20+
* @author Kévin Dunglas <dunglas@gmail.com>
21+
*/
22+
class ResolveNamedArgumentsPass extends AbstractRecursivePass
23+
{
24+
/**
25+
* {@inheritdoc}
26+
*/
27+
protected function processValue($value, $isRoot = false)
28+
{
29+
if (!$value instanceof Definition) {
30+
return parent::processValue($value, $isRoot);
31+
}
32+
33+
$parameterBag = $this->container->getParameterBag();
34+
35+
if ($class = $value->getClass()) {
36+
$class = $parameterBag->resolveValue($class);
37+
}
38+
39+
$calls = $value->getMethodCalls();
40+
$calls[] = array('__construct', $value->getArguments());
41+
42+
foreach ($calls as $i => $call) {
43+
list($method, $arguments) = $call;
44+
$method = $parameterBag->resolveValue($method);
45+
$parameters = null;
46+
47+
foreach ($arguments as $key => $argument) {
48+
if (is_int($key) || '' === $key || '$' !== $key[0]) {
49+
continue;
50+
}
51+
52+
$parameters = null !== $parameters ? $parameters : $this->getParameters($class, $method);
53+
54+
foreach ($parameters as $j => $p) {
55+
if ($key === '$'.$p->name) {
56+
unset($arguments[$key]);
57+
$arguments[$j] = $argument;
58+
59+
continue 2;
60+
}
61+
}
62+
63+
throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": method "%s::%s" has no argument named "%s". Check your service definition.', $this->currentId, $class, $method, $key));
64+
}
65+
66+
if ($arguments !== $call[1]) {
67+
ksort($arguments);
68+
$calls[$i][1] = $arguments;
69+
}
70+
}
71+
72+
list(, $arguments) = array_pop($calls);
73+
74+
if ($arguments !== $value->getArguments()) {
75+
$value->setArguments($arguments);
76+
}
77+
if ($calls !== $value->getMethodCalls()) {
78+
$value->setMethodCalls($calls);
79+
}
80+
81+
return parent::processValue($value, $isRoot);
82+
}
83+
84+
/**
85+
* @param string|null $class
86+
* @param string $method
87+
*
88+
* @throws InvalidArgumentException
89+
*
90+
* @return array
91+
*/
92+
private function getParameters($class, $method)
93+
{
94+
if (!$class) {
95+
throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": the class is not set.', $this->currentId));
96+
}
97+
98+
if (!$r = $this->container->getReflectionClass($class)) {
99+
throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": class "%s" does not exist.', $this->currentId, $class));
100+
}
101+
102+
if (!$r->hasMethod($method)) {
103+
throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": method "%s::%s" does not exist.', $this->currentId, $class, $method));
104+
}
105+
106+
$method = $r->getMethod($method);
107+
if (!$method->isPublic()) {
108+
throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": method "%s::%s" must be public.', $this->currentId, $class, $method->name));
109+
}
110+
111+
return $method->getParameters();
112+
}
113+
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Definition.php
+10-6Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,8 @@ public function addArgument($argument)
190190
/**
191191
* Sets a specific argument.
192192
*
193-
* @param int $index
194-
* @param mixed $argument
193+
* @param int|string $index
194+
* @param mixed $argument
195195
*
196196
* @return $this
197197
*
@@ -203,10 +203,14 @@ public function replaceArgument($index, $argument)
203203
throw new OutOfBoundsException('Cannot replace arguments if none have been configured yet.');
204204
}
205205

206-
if ($index < 0 || $index > count($this->arguments) - 1) {
206+
if (is_int($index) && ($index < 0 || $index > count($this->arguments) - 1)) {
207207
throw new OutOfBoundsException(sprintf('The index "%d" is not in the range [0, %d].', $index, count($this->arguments) - 1));
208208
}
209209

210+
if (!array_key_exists($index, $this->arguments)) {
211+
throw new OutOfBoundsException(sprintf('The argument "%s" doesn\'t exist.', $index));
212+
}
213+
210214
$this->arguments[$index] = $argument;
211215

212216
return $this;
@@ -225,16 +229,16 @@ public function getArguments()
225229
/**
226230
* Gets an argument to pass to the service constructor/factory method.
227231
*
228-
* @param int $index
232+
* @param int|string $index
229233
*
230234
* @return mixed The argument value
231235
*
232236
* @throws OutOfBoundsException When the argument does not exist
233237
*/
234238
public function getArgument($index)
235239
{
236-
if ($index < 0 || $index > count($this->arguments) - 1) {
237-
throw new OutOfBoundsException(sprintf('The index "%d" is not in the range [0, %d].', $index, count($this->arguments) - 1));
240+
if (!array_key_exists($index, $this->arguments)) {
241+
throw new OutOfBoundsException(sprintf('The argument "%s" doesn\'t exist.', $index));
238242
}
239243

240244
return $this->arguments[$index];

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
+17-1Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,22 @@ private function parseDefaults(array &$content, $file)
254254
return $defaults;
255255
}
256256

257+
/**
258+
* @param array $service
259+
*
260+
* @return bool
261+
*/
262+
private function isUsingShortSyntax(array $service)
263+
{
264+
foreach ($service as $key => $value) {
265+
if (is_string($key) && ('' === $key || '$' !== $key[0])) {
266+
return false;
267+
}
268+
}
269+
270+
return true;
271+
}
272+
257273
/**
258274
* Parses a definition.
259275
*
@@ -273,7 +289,7 @@ private function parseDefinition($id, $service, $file, array $defaults)
273289
return;
274290
}
275291

276-
if (is_array($service) && array_values($service) === $service) {
292+
if (is_array($service) && $this->isUsingShortSyntax($service)) {
277293
$service = array('arguments' => $service);
278294
}
279295

+98Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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 Symfony\Component\DependencyInjection\Compiler\ResolveNamedArgumentsPass;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Reference;
17+
use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy;
18+
19+
/**
20+
* @author Kévin Dunglas <dunglas@gmail.com>
21+
*/
22+
class ResolveNamedArgumentsPassTest extends \PHPUnit_Framework_TestCase
23+
{
24+
public function testProcess()
25+
{
26+
$container = new ContainerBuilder();
27+
28+
$definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class);
29+
$definition->setArguments(array(0 => new Reference('foo'), '$apiKey' => '123'));
30+
$definition->addMethodCall('setApiKey', array('$apiKey' => '123'));
31+
32+
$pass = new ResolveNamedArgumentsPass();
33+
$pass->process($container);
34+
35+
$this->assertEquals(array(0 => new Reference('foo'), 1 => '123'), $definition->getArguments());
36+
$this->assertEquals(array(array('setApiKey', array('123'))), $definition->getMethodCalls());
37+
}
38+
39+
/**
40+
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
41+
*/
42+
public function testClassNull()
43+
{
44+
$container = new ContainerBuilder();
45+
46+
$definition = $container->register(NamedArgumentsDummy::class);
47+
$definition->setArguments(array('$apiKey' => '123'));
48+
49+
$pass = new ResolveNamedArgumentsPass();
50+
$pass->process($container);
51+
}
52+
53+
/**
54+
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
55+
*/
56+
public function testClassNotExist()
57+
{
58+
$container = new ContainerBuilder();
59+
60+
$definition = $container->register(NotExist::class, NotExist::class);
61+
$definition->setArguments(array('$apiKey' => '123'));
62+
63+
$pass = new ResolveNamedArgumentsPass();
64+
$pass->process($container);
65+
}
66+
67+
/**
68+
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
69+
*/
70+
public function testClassNoConstructor()
71+
{
72+
$container = new ContainerBuilder();
73+
74+
$definition = $container->register(NoConstructor::class, NoConstructor::class);
75+
$definition->setArguments(array('$apiKey' => '123'));
76+
77+
$pass = new ResolveNamedArgumentsPass();
78+
$pass->process($container);
79+
}
80+
81+
/**
82+
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
83+
*/
84+
public function testArgumentNotFound()
85+
{
86+
$container = new ContainerBuilder();
87+
88+
$definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class);
89+
$definition->setArguments(array('$notFound' => '123'));
90+
91+
$pass = new ResolveNamedArgumentsPass();
92+
$pass->process($container);
93+
}
94+
}
95+
96+
class NoConstructor
97+
{
98+
}
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
/**
6+
* @author Kévin Dunglas <dunglas@gmail.com>
7+
*/
8+
class NamedArgumentsDummy
9+
{
10+
public function __construct(CaseSensitiveClass $c, $apiKey)
11+
{
12+
}
13+
14+
public function setApiKey($apiKey)
15+
{
16+
}
17+
}
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
3+
<services>
4+
<service id="Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy">
5+
<argument key="$apiKey">ABCD</argument>
6+
<call method="setApiKey">
7+
<argument key="$apiKey">123</argument>
8+
</call>
9+
</service>
10+
</services>
11+
</container>
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
services:
2+
Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy: { $apiKey: ABCD }
3+
4+
another_one:
5+
class: Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy
6+
arguments:
7+
$apiKey: ABCD
8+
calls:
9+
- ['setApiKey', { $apiKey: '123' }]

‎src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php
+15Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Symfony\Component\Config\Resource\FileResource;
2525
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
2626
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype;
27+
use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy;
2728
use Symfony\Component\ExpressionLanguage\Expression;
2829

2930
class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
@@ -693,4 +694,18 @@ public function testDefaultsWithAutowiredCalls()
693694
$this->assertSame(array('setFoo'), $container->getDefinition('no_defaults_child')->getAutowiredCalls());
694695
$this->assertSame(array(), $container->getDefinition('with_defaults_child')->getAutowiredCalls());
695696
}
697+
698+
public function testNamedArguments()
699+
{
700+
$container = new ContainerBuilder();
701+
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
702+
$loader->load('services_named_args.xml');
703+
704+
$this->assertEquals(array('$apiKey' => 'ABCD'), $container->getDefinition(NamedArgumentsDummy::class)->getArguments());
705+
706+
$container->compile();
707+
708+
$this->assertEquals(array(1 => 'ABCD'), $container->getDefinition(NamedArgumentsDummy::class)->getArguments());
709+
$this->assertEquals(array(array('setApiKey', array('123'))), $container->getDefinition(NamedArgumentsDummy::class)->getMethodCalls());
710+
}
696711
}

‎src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Symfony\Component\Config\Resource\FileResource;
2525
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
2626
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype;
27+
use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy;
2728
use Symfony\Component\ExpressionLanguage\Expression;
2829

2930
class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
@@ -440,6 +441,22 @@ public function testGetter()
440441
$this->assertEquals(array('getbar' => array('bar' => new Reference('bar'))), $container->getDefinition('foo')->getOverriddenGetters());
441442
}
442443

444+
public function testNamedArguments()
445+
{
446+
$container = new ContainerBuilder();
447+
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
448+
$loader->load('services_named_args.yml');
449+
450+
$this->assertEquals(array('$apiKey' => 'ABCD'), $container->getDefinition(NamedArgumentsDummy::class)->getArguments());
451+
$this->assertEquals(array('$apiKey' => 'ABCD'), $container->getDefinition('another_one')->getArguments());
452+
453+
$container->compile();
454+
455+
$this->assertEquals(array(1 => 'ABCD'), $container->getDefinition(NamedArgumentsDummy::class)->getArguments());
456+
$this->assertEquals(array(1 => 'ABCD'), $container->getDefinition('another_one')->getArguments());
457+
$this->assertEquals(array(array('setApiKey', array('123'))), $container->getDefinition('another_one')->getMethodCalls());
458+
}
459+
443460
/**
444461
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
445462
* @expectedExceptionMessage The value of the "decorates" option for the "bar" service must be the id of the service without the "@" prefix (replace "@foo" with "foo").

0 commit comments

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