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

Skip to content

Commit 7a00fdb

Browse files
committed
[DependencyInjection] Support anonymous services in Yaml
1 parent dd7c727 commit 7a00fdb

File tree

7 files changed

+165
-13
lines changed

7 files changed

+165
-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: 45 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
*/
@@ -133,14 +135,15 @@ public function load($resource, $type = null)
133135
}
134136

135137
foreach ($content['parameters'] as $key => $value) {
136-
$this->container->setParameter($key, $this->resolveServices($value));
138+
$this->container->setParameter($key, $this->resolveServices($value, $resource, true));
137139
}
138140
}
139141

140142
// extensions
141143
$this->loadFromExtensions($content);
142144

143145
// services
146+
$this->anonymousServicesCount = 0;
144147
$this->setCurrentDir(dirname($path));
145148
try {
146149
$this->parseDefinitions($content, $resource);
@@ -416,19 +419,19 @@ private function parseDefinition($id, $service, $file, array $defaults)
416419
}
417420

418421
if (isset($service['arguments'])) {
419-
$definition->setArguments($this->resolveServices($service['arguments']));
422+
$definition->setArguments($this->resolveServices($service['arguments'], $file));
420423
}
421424

422425
if (isset($service['properties'])) {
423-
$definition->setProperties($this->resolveServices($service['properties']));
426+
$definition->setProperties($this->resolveServices($service['properties'], $file));
424427
}
425428

426429
if (isset($service['configurator'])) {
427430
$definition->setConfigurator($this->parseCallable($service['configurator'], 'configurator', $id, $file));
428431
}
429432

430433
if (isset($service['getters'])) {
431-
$definition->setOverriddenGetters($this->resolveServices($service['getters']));
434+
$definition->setOverriddenGetters($this->resolveServices($service['getters'], $file));
432435
}
433436

434437
if (isset($service['calls'])) {
@@ -439,10 +442,10 @@ private function parseDefinition($id, $service, $file, array $defaults)
439442
foreach ($service['calls'] as $call) {
440443
if (isset($call['method'])) {
441444
$method = $call['method'];
442-
$args = isset($call['arguments']) ? $this->resolveServices($call['arguments']) : array();
445+
$args = isset($call['arguments']) ? $this->resolveServices($call['arguments'], $file) : array();
443446
} else {
444447
$method = $call[0];
445-
$args = isset($call[1]) ? $this->resolveServices($call[1]) : array();
448+
$args = isset($call[1]) ? $this->resolveServices($call[1], $file) : array();
446449
}
447450

448451
$definition->addMethodCall($method, $args);
@@ -553,15 +556,15 @@ private function parseCallable($callable, $parameter, $id, $file)
553556
if (false !== strpos($callable, ':') && false === strpos($callable, '::')) {
554557
$parts = explode(':', $callable);
555558

556-
return array($this->resolveServices('@'.$parts[0]), $parts[1]);
559+
return array($this->resolveServices('@'.$parts[0], $file), $parts[1]);
557560
}
558561

559562
return $callable;
560563
}
561564

562565
if (is_array($callable)) {
563566
if (isset($callable[0]) && isset($callable[1])) {
564-
return array($this->resolveServices($callable[0]), $callable[1]);
567+
return array($this->resolveServices($callable[0], $file), $callable[1]);
565568
}
566569

567570
if ('factory' === $parameter && isset($callable[1]) && null === $callable[0]) {
@@ -653,11 +656,13 @@ private function validate($content, $file)
653656
/**
654657
* Resolves services.
655658
*
656-
* @param mixed $value
659+
* @param mixed $value
660+
* @param string $file
661+
* @param bool $isParameter
657662
*
658663
* @return array|string|Reference|ArgumentInterface
659664
*/
660-
private function resolveServices($value)
665+
private function resolveServices($value, $file, $isParameter = false)
661666
{
662667
if ($value instanceof TaggedValue) {
663668
$argument = $value->getValue();
@@ -666,7 +671,7 @@ private function resolveServices($value)
666671
throw new InvalidArgumentException('"!iterator" tag only accepts sequences.');
667672
}
668673

669-
return new IteratorArgument($this->resolveServices($argument));
674+
return new IteratorArgument($this->resolveServices($argument, $file, $isParameter));
670675
}
671676
if ('service_locator' === $value->getTag()) {
672677
if (!is_array($argument)) {
@@ -679,7 +684,7 @@ private function resolveServices($value)
679684
}
680685
}
681686

682-
return new ServiceLocatorArgument($this->resolveServices($argument));
687+
return new ServiceLocatorArgument($this->resolveServices($argument, $file, $isParameter));
683688
}
684689
if ('closure_proxy' === $value->getTag()) {
685690
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 +701,39 @@ private function resolveServices($value)
696701

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

700730
throw new InvalidArgumentException(sprintf('Unsupported tag "!%s".', $value->getTag()));
701731
}
702732

703733
if (is_array($value)) {
704-
$value = array_map(array($this, 'resolveServices'), $value);
734+
foreach ($value as &$v) {
735+
$v = $this->resolveServices($v, $file, $isParameter);
736+
}
705737
} elseif (is_string($value) && 0 === strpos($value, '@=')) {
706738
return new Expression(substr($value, 2));
707739
} elseif (is_string($value) && 0 === strpos($value, '@')) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
imports:
2+
# Ensure the anonymous services count is reset after importing a file
3+
- { resource: anonymous_services_in_instanceof.yml }
4+
5+
services:
6+
_defaults:
7+
autowire: true
8+
9+
Foo:
10+
arguments:
11+
- !service
12+
class: Bar
13+
autowire: true
14+
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+
DummyInterface:
8+
arguments: [ !service { class: Anonymous } ]
9+
10+
# Ensure next conditionals are not considered as services
11+
Bar:
12+
autowire: true
13+
14+
Dummy: ~
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: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,88 @@ 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 an argument
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+
$this->assertStringStartsWith('2', (string) $args[0]);
528+
529+
$anonymous = $container->getDefinition((string) $args[0]);
530+
$this->assertEquals('Bar', $anonymous->getClass());
531+
$this->assertFalse($anonymous->isPublic());
532+
$this->assertTrue($anonymous->isAutowired());
533+
534+
// Anonymous service in a callable
535+
$factory = $definition->getFactory();
536+
$this->assertInternalType('array', $factory);
537+
$this->assertInstanceOf(Reference::class, $factory[0]);
538+
$this->assertTrue($container->has((string) $factory[0]));
539+
$this->assertStringStartsWith('1', (string) $factory[0]);
540+
$this->assertEquals('constructFoo', $factory[1]);
541+
542+
$anonymous = $container->getDefinition((string) $factory[0]);
543+
$this->assertEquals('Quz', $anonymous->getClass());
544+
$this->assertFalse($anonymous->isPublic());
545+
$this->assertFalse($anonymous->isAutowired());
546+
}
547+
548+
public function testAnonymousServicesInInstanceof()
549+
{
550+
$container = new ContainerBuilder();
551+
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
552+
$loader->load('anonymous_services_in_instanceof.yml');
553+
554+
$definition = $container->getDefinition('Dummy');
555+
556+
$instanceof = $definition->getInstanceofConditionals();
557+
$this->assertCount(3, $instanceof);
558+
$this->assertArrayHasKey('DummyInterface', $instanceof);
559+
560+
$args = $instanceof['DummyInterface']->getArguments();
561+
$this->assertCount(1, $args);
562+
$this->assertInstanceOf(Reference::class, $args[0]);
563+
$this->assertTrue($container->has((string) $args[0]));
564+
565+
$anonymous = $container->getDefinition((string) $args[0]);
566+
$this->assertEquals('Anonymous', $anonymous->getClass());
567+
$this->assertFalse($anonymous->isPublic());
568+
$this->assertEmpty($anonymous->getInstanceofConditionals());
569+
570+
$this->assertFalse($container->has('Bar'));
571+
}
572+
573+
/**
574+
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
575+
* @expectedExceptionMessage Creating an alias using the tag "!service" is not allowed in "anonymous_services_alias.yml".
576+
*/
577+
public function testAnonymousServicesWithAliases()
578+
{
579+
$container = new ContainerBuilder();
580+
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
581+
$loader->load('anonymous_services_alias.yml');
582+
}
583+
584+
/**
585+
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
586+
* @expectedExceptionMessage Using an anonymous service in a parameter is not allowed in "anonymous_services_in_parameters.yml".
587+
*/
588+
public function testAnonymousServicesInParameters()
589+
{
590+
$container = new ContainerBuilder();
591+
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
592+
$loader->load('anonymous_services_in_parameters.yml');
593+
}
512594
}
513595

514596
interface FooInterface

0 commit comments

Comments
 (0)
0