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 7a00fdb

Browse filesBrowse files
committed
[DependencyInjection] Support anonymous services in Yaml
1 parent dd7c727 commit 7a00fdb
Copy full SHA for 7a00fdb

File tree

7 files changed

+165
-13
lines changed
Filter options

7 files changed

+165
-13
lines changed

‎src/Symfony/Component/DependencyInjection/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/CHANGELOG.md
+1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
+45-13Lines 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, '@')) {
+14Lines 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' ]
+7Lines 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
+14Lines 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: ~
+2Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php
+82Lines 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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.