8000 [DI] Replace container injection by explicit service locators · symfony/symfony@b2b185f · GitHub
[go: up one dir, main page]

Skip to content

Commit b2b185f

Browse files
committed
[DI] Replace container injection by explicit service locators
1 parent 554b1a7 commit b2b185f

32 files changed

+374
-1
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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\Argument;
13+
14+
use Symfony\Component\DependencyInjection\Reference;
15+
16+
/**
17+
* Represents a service locator able to lazy load a given range of services.
18+
*
19+
* @author Robin Chalas <robin.chalas@gmail.com>
20+
*/
21+
class LocatorArgument implements ArgumentInterface
22+
{
23+
private $values;
24+
25+
/**
26+
* @param Reference[] $values
27+
*/
28+
public function __construct(array $values)
29+
{
30+
$this->values = $values;
31+
}
32+
33+
public function getValues()
34+
{
35+
return $this->values;
36+
}
37+
38+
public function setValues(array $values)
39+
{
40+
$this->values = $values;
41+
}
42+
}
Lines 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\Argument;
13+
14+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
15+
16+
/**
17+
* @author Robin Chalas <robin.chalas@gmail.com>
18+
*/
19+
final class ServiceLocator
20+
{
21+
private $hasser;
22+
private $getter;
23+
24+
public function __construct(callable $hasser, callable $getter)
25+
{
26+
$this->hasser = $hasser;
27+
$this->getter = $getter;
28+
}
29+
30+
public function get($id)
31+
{
32+
if (!$this->has($id)) {
33+
throw new InvalidArgumentException(sprintf('Cannot get service "%s" as it has not been explictly made accessible to this service locator.', $id));
34+
}
35+
36+
$getter = $this->getter;
37+
38+
return $getter($id);
39+
}
40+
41+
public function has($id)
42+
{
43+
$hasser = $this->hasser;
44+
45+
return $hasser($id);
46+
}
47+
}

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1515
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
16+
use Symfony\Component\DependencyInjection\Argument\LocatorArgument;
1617
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
18+
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
1719
use Symfony\Component< F43C /span>\DependencyInjection\Compiler\Compiler;
1820
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1921
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
@@ -1090,6 +1092,17 @@ public function resolveServices($value)
10901092
foreach ($value as $k => $v) {
10911093
$value[$k] = $this->resolveServices($v);
10921094
}
1095+
} elseif ($value instanceof LocatorArgument) {
1096+
$references = $value->getValues();
1097+
$value = new ServiceLocator(function ($id) use ($references) {
1098+
return isset($references[$id]) || in_array($id, array_map(function ($ref) { return (string) $ref; }, $references), true);
1099+
}, function ($id) use ($references) {
1100+
if (isset($references[$id])) {
1101+
return $this->get($references[$id], $references[$id]->getInvalidBehavior());
1102+
}
1103+
1104+
return $this->get($id, $references[array_search($id, $references)]->getInvalidBehavior());
1105+
});
10931106
} elseif ($value instanceof IteratorArgument) {
10941107
$parameterBag = $this->getParameterBag();
10951108
$value = new RewindableGenerator(function () use ($value, $parameterBag) {

src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1515
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
16+
use Symfony\Component\DependencyInjection\Argument\LocatorArgument;
1617
use Symfony\Component\DependencyInjection\Variable;
1718
use Symfony\Component\DependencyInjection\Definition;
1819
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -881,6 +882,7 @@ private function startClass($class, $baseClass, $namespace)
881882
<?php
882883
$namespaceLine
883884
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
885+
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
884886
use Symfony\Component\DependencyInjection\ContainerInterface;
885887
use Symfony\Component\DependencyInjection\Container;
886888
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@@ -1525,6 +1527,24 @@ private function dumpValue($value, $interpolate = true)
15251527
}
15261528

15271529
return sprintf('array(%s)', implode(', ', $code));
1530+
} elseif ($value instanceof LocatorArgument) {
1531+
$services = $value->getValues();
1532+
$servicesMap = array();
1533+
$getterCode = 'function ($id) {'."\n";
1534+
foreach ($services as $k => $ref) {
1535+
$servicesMap[] = sprintf("'%s' => '%s'", is_string($k) ? $k : $ref, $ref);
1536+
$singleGetter = sprintf(' if (\'%s\' === $id) {', is_string($k) ? $k : $ref);
1537+
$singleGetter .= sprintf("\n return %s;", $this->getServiceCall((string) $ref, $ref));
1538+
$singleGetter .= "\n }\n";
1539+
$getterCode .= $singleGetter;
1540+
}
1541+
$getterCode .= ' }';
1542+
$hasserCode = 'function ($id) {'."\n";
1543+
$hasserCode .= sprintf(' $serviceMap = array(%s);', implode(', ', $servicesMap));
1544+
$hasserCode .= "\n\n".' return isset($serviceMap[$id]);';
1545+
$hasserCode .= "\n }";
1546+
1547+
return sprintf('new ServiceLocator(%s, %s)', $hasserCode, $getterCode);
15281548
} elseif ($value instanceof IteratorArgument) {
15291549
$countCode = array();
15301550
$countCode[] = 'function () {';

src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1515
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
16+
use Symfony\Component\DependencyInjection\Argument\LocatorArgument;
1617
use Symfony\Component\DependencyInjection\ContainerInterface;
1718
use Symfony\Component\DependencyInjection\Parameter;
1819
use Symfony\Component\DependencyInjection\Reference;
@@ -291,6 +292,9 @@ private function convertParameters(array $parameters, $type, \DOMElement $parent
291292
if (is_array($value)) {
292293
$element->setAttribute('type', 'collection');
293294
$this->convertParameters($value, $type, $element, 'key');
295+
} elseif ($value instanceof LocatorArgument) {
296+
$element->setAttribute('type', 'locator');
297+
$this->convertParameters($value->getValues(), $type, $element);
294298
} elseif ($value instanceof IteratorArgument) {
295299
$element->setAttribute('type', 'iterator');
296300
$this->convertParameters($value->getValues(), $type, $element, 'key');

src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\DependencyInjection\Alias;
1616
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1717
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
18+
use Symfony\Component\DependencyInjection\Argument\LocatorArgument;
1819
use Symfony\Component\DependencyInjection\ContainerInterface;
1920
use Symfony\Component\DependencyInjection\Definition;
2021
use Symfony\Component\DependencyInjection\Parameter;
@@ -251,7 +252,9 @@ private function dumpCallable($callable)
251252
*/
252253
private function dumpValue($value)
253254
{
254-
if ($value instanceof IteratorArgument) {
255+
if ($value instanceof LocatorArgument) {
256+
$value = array('=locator' => $value->getValues());
257+
} elseif ($value instanceof IteratorArgument) {
255258
$value = array('=iterator' => $value->getValues());
256259
} elseif ($value instanceof ClosureProxyArgument) {
257260
$value = array('=closure_proxy' => $value->getValues());

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\DependencyInjection\Alias;
1818
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1919
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
20+
use Symfony\Component\DependencyInjection\Argument\LocatorArgument;
2021
use Symfony\Component\DependencyInjection\Definition;
2122
use Symfony\Component\DependencyInjection\ChildDefinition;
2223
use Symfony\Component\DependencyInjection\Reference;
@@ -493,6 +494,9 @@ private function getArgumentsAsPhp(\DOMElement $node, $name, $lowercase = true)
493494
case 'iterator':
494495
$arguments[$key] = new IteratorArgument($this->getArgumentsAsPhp($arg, $name, false));
495496
break;
497+
case 'locator':
498+
$arguments[$key] = new LocatorArgument($this->getArgumentsAsPhp($arg, $name, false));
499+
break;
496500
case 'string':
497501
$arguments[$key] = $arg->nodeValue;
498502
break;

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\DependencyInjection\Alias;
1515
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1616
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
17+
use Symfony\Component\DependencyInjection\Argument\LocatorArgument;
1718
use Symfony\Component\DependencyInjection\ChildDefinition;
1819
use Symfony\Component\DependencyInjection\ContainerInterface;
1920
use Symfony\Component\DependencyInjection\Definition;
@@ -572,6 +573,14 @@ private function resolveServices($value)
572573
throw new InvalidArgumentException('Arguments typed "=iterator" must be arrays.');
573574
}
574575
$value = new IteratorArgument(array_map(array($this, 'resolveServices'), $value));
576+
} elseif (array_key_exists('=locator', $value)) {
577+
if (1 !== count($value)) {
578+
throw new InvalidArgumentException('Arguments typed "=locator" must have no sibling keys.');
579+
}
580+
if (!is_array($value = $value['=locator'])) {
581+
throw new InvalidArgumentException('Arguments typed "=locator" must be arrays.');
582+
}
583+
$value = new LocatorArgument(array_map(array($this, 'resolveServices'), $value));
575584
} elseif (array_key_exists('=closure_proxy', $value)) {
576585
if (1 !== count($value)) {
577586
throw new InvalidArgumentException('Arguments typed "=closure_proxy" must have no sibling keys.');

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@
222222
<xsd:enumeration value="string" />
223223
<xsd:enumeration value="constant" />
224224
<xsd:enumeration value="iterator" />
225+
<xsd:enumeration value="locator" />
225226
<xsd:enumeration value="closure-proxy" />
226227
</xsd:restriction>
227228
</xsd:simpleType>

src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
1919
use Symfony\Component\DependencyInjection\Alias;
2020
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
2121
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
22+
use Symfony\Component\DependencyInjection\Argument\LocatorArgument;
2223
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
24+
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
2325
use Symfony\Component\DependencyInjection\ChildDefinition;
2426
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
2527
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -436,6 +438,21 @@ public function testCreateServiceWithIteratorArgument()
436438
$this->assertEquals(1, $i);
437439
}
438440

441+
public function testCreateServiceWithLocatorArgument()
442+
{
443+
$builder = new ContainerBuilder();
444+
$builder->register('bar', 'stdClass');
445+
$builder
446+
->register('lazy_context', 'LazyContext')
447+
->setArguments(array(new LocatorArgument(array(new Reference('bar'), new Reference('invalid', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)))))
448+
;
449+
450+
$lazyContext = $builder->get('lazy_context');
451+
$this->assertInstanceOf(ServiceLocator::class, $lazyContext->lazyValues);
452+
$this->assertInstanceOf('\stdClass', $lazyContext->lazyValues->get('bar'));
453+
$this->assertNull($lazyContext->lazyValues->get('invalid'));
454+
}
455+
439456
/**
440457
* @expectedException \RuntimeException
441458
*/

src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414
use DummyProxyDumper;
1515
use Symfony\Component\Config\FileLocator;
1616
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
17+
use Symfony\Component\DependencyInjection\Argument\LocatorArgument;
18+
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
1719
use Symfony\Component\DependencyInjection\ContainerBuilder;
1820
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
1921
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
2022
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
2123
use Symfony\Component\DependencyInjection\Reference;
2224
use Symfony\Component\DependencyInjection\Definition;
2325
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
26+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
2427
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
2528
use Symfony\Component\DependencyInjection\Variable;
2629
use Symfony\Component\ExpressionLanguage\Expression;
@@ -495,6 +498,43 @@ public function testCircularReferenceAllowanceForInlinedDefinitionsForLazyServic
495498
$dumper->dump();
496499
}
497500

501+
public function testLocatorArgumentProvideServiceLocator()
502+
{
503+
require_once self::$fixturesPath.'/includes/classes.php';
504+
505+
$container = new ContainerBuilder();
506+
$container->register('lazy_referenced', 'stdClass');
507+
$container
508+
->register('lazy_context', 'LazyContext')
509+
->setArguments(array(new LocatorArgument(array('lazy1' => new Reference('lazy_referenced'), 'lazy2' => new Reference('lazy_referenced'), new Reference('service_container')))))
510+
;
511+
$container->compile();
512+
513+
$dumper = new PhpDumper($container);
514+
$dump = $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Locator_Argument_Provide_Service_Locator'));
515+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_locator_argument.php', $dump);
516+
517+
require_once(self::$fixturesPath.'/php/services_locator_argument.php');
518+
519+
$container = new \Symfony_DI_PhpDumper_Test_Locator_Argument_Provide_Service_Locator();
520+
$lazyContext = $container->get('lazy_context');
521+
522+
$this->assertInstanceOf(ServiceLocator::class, $lazyContext->lazyValues);
523+
$this->assertSame($container, $lazyContext->lazyValues->get('service_container'));
524+
$this->assertInstanceOf('stdClass', $lazy1 = $lazyContext->lazyValues->get('lazy1'));
525+
$this->assertInstanceOf('stdClass', $lazy2 = $lazyContext->lazyValues->get('lazy2'));
526+
$this->assertSame($lazy1, $lazy2);
527+
528+
$this->assertFalse($lazyContext->lazyValues->has('lazy_referenced'), 'If a key is defined for a service then ->has() returns false for the original service id');
529+
try {
530+
$lazyContext->lazyValues->get('lazy_referenced');
531+
$this->fail('If a key is defined for a service then only the key can be used for accessing it using ->get()');
532+
} catch (\Exception $e) {
533+
$this->assertInstanceOf(InvalidArgumentException::class, $e, '->dump() throws a RuntimeException if the container to be dumped has reference to objects or resources');
534+
$this->assertEquals('Cannot get service "lazy_referenced" as it has not been explictly made accessible to this service locator.', $e->getMessage());
535+
}
536+
}
537+
498538
public function testLazyArgumentProvideGenerator()
499539
{
500540
require_once self::$fixturesPath.'/includes/classes.php';

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1-1.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
namespace Symfony\Component\DependencyInjection\Dump;
33

44
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
5+
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
56
use Symfony\Component\DependencyInjection\ContainerInterface;
67
use Symfony\Component\DependencyInjection\Container;
78
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services1.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
4+
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
45
use Symfony\Component\DependencyInjection\ContainerInterface;
56
use Symfony\Component\DependencyInjection\Container;
67
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
4+
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
45
use Symfony\Component\DependencyInjection\ContainerInterface;
56
use Symfony\Component\DependencyInjection\Container;
67
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services12.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
4+
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
45
use Symfony\Component\DependencyInjection\ContainerInterface;
56
use Symfony\Component\DependencyInjection\Container;
67
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services13.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
4+
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
45
use Symfony\Component\DependencyInjection\ContainerInterface;
56
use Symfony\Component\DependencyInjection\Container;
67
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services19.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
4+
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
45
use Symfony\Component\DependencyInjection\ContainerInterface;
56
use Symfony\Component\DependencyInjection\Container;
67
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services24.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
4+
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
45
use Symfony\Component\DependencyInjection\ContainerInterface;
56
use Symfony\Component\DependencyInjection\Container;
67
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;

0 commit comments

Comments
 (0)
0