Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6e50129

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

File tree

10 files changed

+308
-7
lines changed

10 files changed

+308
-7
lines changed

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

Lines 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(),
Lines 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 <[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\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 <[email protected]>
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

Lines 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

Lines 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

Lines 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 <[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\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 <[email protected]>
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+
}
Lines 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 <[email protected]>
7+
*/
8+
class NamedArgumentsDummy
9+
{
10+
public function __construct(CaseSensitiveClass $c, $apiKey)
11+
{
12+
}
13+
14+
public function setApiKey($apiKey)
15+
{
16+
}
17+
}
Lines 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>
Lines 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

Lines 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

Lines 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)