8000 [DependencyInjection] Support anonymous services in Yaml · symfony/symfony@5ba0c74 · GitHub
[go: up one dir, main page]

Skip to content
65FF

Commit 5ba0c74

Browse files
committed
[DependencyInjection] Support anonymous services in Yaml
1 parent dd7c727 commit 5ba0c74

File tree

7 files changed

+161
-13
lines changed

7 files changed

+161
-13
lines changed

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
3.3.0
55
-----
66

7+
* added anonymous services support in YAML configuration files using the `!service` tag.
78
* [EXPERIMENTAL] added "TypedReference" and "ServiceClosureArgument" for creating service-locator services
89
* [EXPERIMENTAL] added "instanceof" section for local interface-defined configs
910
* added "service-locator" argument for lazy loading a set of identified values and services

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

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ class YamlFileLoader extends FileLoader
107107

108108
private $yamlParser;
109109

110+
private $anonymousServicesCount;
111+
110112
/**
111113
* {@inheritdoc}
112114
*/
@@ -123,6 +125,8 @@ public function load($resource, $type = null)
123125
return;
124126
}
125127

128+
$this->anonymousServicesCount = 0;
129+
126130
// imports
127131
$this->parseImports($content, $path);
128132

@@ -133,7 +137,7 @@ public function load($resource, $type = null)
133137
}
134138

135139
foreach ($content['parameters'] as $key => $value) {
136-
$this->container->setParameter($key, $this->resolveServices($value));
140+
$this->container->setParameter($key, $this->resolveServices($value, $resource, true));
137141
}
138142
}
139143

@@ -416,19 +420,19 @@ private function parseDefinition($id, $service, $file, array $defaults)
416420
}
417421

418422
if (isset($service['arguments'])) {
419-
$definition->setArguments($this->resolveServices($service['arguments']));
423+
$definition->setArguments($this->resolveServices($service['arguments'], $file));
420424
}
421425

422426
if (isset($service['properties'])) {
423-
$definition->setProperties($this->resolveServices($service['properties']));
427+
$definition->setProperties($this->resolveServices($service['properties'], $file));
424428
}
425429

426430
if (isset($service['configurator'])) {
427431
$definition->setConfigurator($this->parseCallable($service['configurator'], 'configurator', $id, $file));
428432
}
429433

430434
if (isset($service['getters'])) {
431-
$definition->setOverriddenGetters($this->resolveServices($service['getters']));
435+
$definition->setOverriddenGetters($this->resolveServices($service['getters'], $file));
432436
}
433437

434438
if (isset($service['calls'])) {
@@ -439,10 +443,10 @@ private function parseDefinition($id, $service, $file, array $defaults)
439443
foreach ($service['calls'] as $call) {
440444
if (isset($call['method'])) {
441445
$method = $call['method'];
442-
$args = isset($call['arguments']) ? $this->resolveServices($call['arguments']) : array();
446+
$args = isset($call['arguments']) ? $this->resolveServices($call['arguments'], $file) : array();
443447
} else {
444448
$method = $call[0];
445-
$args = isset($call[1]) ? $this->resolveServices($call[1]) : array();
449+
$args = isset($call[1]) ? $this->resolveServices($call[1], $file) : array();
446450
}
447451

448452
$definition->addMethodCall($method, $args);
@@ -538,6 +542,7 @@ private function parseDefinition($id, $service, $file, array $defaults)
538542
* @param string $parameter A parameter (e.g. 'factory' or 'configurator')
539543
* @param string $id A service identifier
540544
* @param string $file A parsed file
545+
* @param array $defaults
541546
*
542547
* @throws InvalidArgumentException When errors are occuried
543548
*
@@ -553,15 +558,15 @@ private function parseCallable($callable, $parameter, $id, $file)
553558
if (false !== strpos($callable, ':') && false === strpos($callable, '::')) {
554559
$parts = explode(':', $callable);
555560

556-
return array($this->resolveServices('@'.$parts[0]), $parts[1]);
561+
return array($this->resolveServices('@'.$parts[0], $file), $parts[1]);
557562
}
558563

559564
return $callable;
560565
}
561566

562567
if (is_array($callable)) {
563568
if (isset($callable[0]) && isset($callable[1])) {
564-
return array($this->resolveServices($callable[0]), $callable[1]);
569+
return array($this->resolveServices($callable[0], $file), $callable[1]);
565570
}
566571

567572
if ('factory' === $parameter && isset($callable[1]) && null === $callable[0]) {
@@ -653,11 +658,13 @@ private function validate($content, $file)
653658
/**
654659
* Resolves services.
655660
*
656-
* @param mixed $value
661+
* @param mixed $value
662+
* @param string $file
663+
* @param bool $isParameter
657664
*
658665
* @return array|string|Reference|ArgumentInterface
659666
*/
660-
private function resolveServices($value)
667+
private function resolveServices($value, $file, $isParameter = false)
661668
{
662669
if ($value instanceof TaggedValue) {
663670
$argument = $value->getValue();
@@ -666,7 +673,7 @@ private function resolveServices($value)
666673
throw new InvalidArgumentException('"!iterator" tag only accepts sequences.');
667674
}
668675

669-
return new IteratorArgument($this->resolveServices($argument));
676+
return new IteratorArgument($this->resolveServices($argument, $file));
670677
}
671678
if ('service_locator' === $value->getTag()) {
672679
if (!is_array($argument)) {
@@ -679,7 +686,7 @@ private function resolveServices($value)
679686
}
680687
}
681688

682-
return new ServiceLocatorArgument($this->resolveServices($argument));
689+
return new ServiceLocatorArgument($this->resolveServices($argument, $file));
683690
}
684691
if ('closure_proxy' === $value->getTag()) {
685692
if (!is_array($argument) || array(0, 1) !== array_keys($argument) || !is_string($argument[0]) || !is_string($argument[1]) || 0 !== strpos($argument[0], '@') || 0 === strpos($argument[0], '@@')) {
@@ -696,12 +703,39 @@ private function resolveServices($value)
696703

697704
return new ClosureProxyArgument($argument[0], $argument[1], $invalidBehavior);
698705
}
706+
// Anonymous service
707+
if ('service' === $value->getTag()) {
708+
if ($isParameter) {
709+
throw new InvalidArgumentException(sprintf('Using an anonymous service in a parameter is not allowed in "%s".', $file));
710+
}
711+
712+
$isLoadingInstanceof = $this->isLoadingInstanceof;
713+
$this->isLoadingInstanceof = false;
714+
$instanceof = $this->instanceof;
715+
$this->instanceof = array();
716+
717+
$id = sprintf('%d_%s', ++$this->anonymousServicesCount, hash('sha256', $file));
718+
$this->parseDefinition($id, $argument, $file, array());
719+
720+
if (!$this->container->hasDefinition($id)) {
721+
throw new InvalidArgumentException(sprintf('Creating an alias using the tag "!service" is not allowed in "%s".', $file));
722+
}
723+
724+
$this->container->getDefinition($id)->setPublic(false);
725+
726+
$this->isLoadingInstanceof = $isLoadingInstanceof;
727+
$this->instanceof = $instanceof;
728+
729+
return new Reference($id);
730+
}
699731

700732
throw new InvalidArgumentException(sprintf('Unsupported tag "!%s".', $value->getTag()));
701733
}
702734

703735
if (is_array($value)) {
704-
$value = array_map(array($this, 'resolveServices'), $value);
736+
foreach ($value as &$v) {
737+
$v = $this->resolveServices($v, $file);
738+
}
705739
} elseif (is_string($value) && 0 === strpos($value, '@=')) {
706740
return new Expression(substr($value, 2));
707741
} elseif (is_string($value) && 0 === strpos($value, '@')) {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
services:
2+
_defaults:
3+
autowire: true
4+
5+
Foo:
6+
arguments:
7+
- !service
8+
class: Bar
9+
autowire: true
10+
factory: [ !service { class: Quz }, 'constructFoo' ]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
services:
2+
Bar: ~
3+
4+
Foo:
5+
arguments:
6+
- !service
7+
alias: Bar
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
services:
2+
_instanceof:
3+
# Ensure previous conditionals aren't applied on anonymous services
4+
Quz:
5+
autowire: true
6+
7+
FooInterface:
8+
arguments: [ !service { class: Anonymous } ]
9+
10+
# Ensure next conditionals are not considered as services
11+
Bar:
12+
autowire: true
13+
14+
Foo: ~
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
parameters:
2+
foo: !service { }

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,86 @@ public function testUnderscoreServiceId()
509509
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
510510
$loader->load('services_underscore.yml');
511511
}
512+
513+
public function testAnonymousServices()
514+
{
515+
$container = new ContainerBuilder();
516+
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
517+
$loader->load('anonymous_services.yml');
518+
519+
$definition = $container->getDefinition('Foo');
520+
$this->assertTrue($definition->isAutowired());
521+
522+
// Anonymous service in a normal service
523+
$args = $definition->getArguments();
524+
$this->assertCount(1, $args);
525+
$this->assertInstanceOf(Reference::class, $args[0]);
526+
$this->assertTrue($container->has((string) $args[0]));
527+
528+
$anonymous = $container->getDefinition((string) $args[0]);
529+
$this->assertEquals('Bar', $anonymous->getClass());
530+
$this->assertFalse($anonymous->isPublic());
531+
$this->assertTrue($anonymous->isAutowired());
532+
533+
// Anonymous service in a callable
534+
$factory = $definition->getFactory();
535+
$this->assertInternalType('array', $factory);
536+
$this->assertInstanceOf(Reference::class, $factory[0]);
537+
$this->assertTrue($container->has((string) $factory[0]));
538+
$this->assertEquals('constructFoo', $factory[1]);
539+
540+
$anonymous = $container->getDefinition((string) $factory[0]);
541+
$this->assertEquals('Quz', $anonymous->getClass());
542+
$this->assertFalse($anonymous->isPublic());
543+
$this->assertFalse($anonymous->isAutowired());
544+
}
545+
546+
public function testAnonymousServicesInInstanceof()
547+
{
548+
$container = new ContainerBuilder();
549+
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
550+
$loader->load('anonymous_services_in_instanceof.yml');
551+
552+
$definition = $container->getDefinition('Foo');
553+
554+
$instanceof = $definition->getInstanceofConditionals();
555+
$this->assertCount(3, $instanceof);
556+
$this->assertArrayHasKey('FooInterface', $instanceof);
557+
558+
$args = $instanceof['FooInterface']->getArguments();
559+
$this->assertCount(1, $args);
560+
$this->assertInstanceOf(Reference::class, $args[0]);
561+
$this->assertTrue($container->has((string) $args[0]));
562+
563+
$anonymous = $container->getDefinition((string) $args[0]);
564+
$this->assertEquals('Anonymous', $anonymous->getClass());
565+
$this->assertFalse($anonymous->isPublic());
566+
$this->assertEmpty($anonymous->getInstanceofConditionals());
567+
568+
$this->assertFalse($container->has('Bar'));
569+
}
570+
571+
/**
572+
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
573+
* @expectedExceptionMessage Creating an alias using the tag "!service" is not allowed in "anonymous_services_alias.yml".
574+
*/
575+
public function testAnonymousServicesWithAliases()
576+
{
577+
$container = new ContainerBuilder();
578+
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
579+
$loader->load('anonymous_services_alias.yml');
580+
}
581+
582+
/**
583+
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
584+
* @expectedExceptionMessage Using an anonymous service in a parameter is not allowed in "anonymous_services_in_parameters.yml".
585+
*/
586+
public function testAnonymousServicesInParameters()
587+
{
588+
$container = new ContainerBuilder();
589+
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
590+
$loader->load('anonymous_services_in_parameters.yml');
591+
}
512592
}
513593

514594
interface FooInterface

0 commit comments

Comments
 (0)
0