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 5ba0c74

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

File tree

7 files changed

+161
-13
lines changed
Filter options

7 files changed

+161
-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
+47-13Lines 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, '@')) {
+10Lines 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' ]
+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+
FooInterface:
8+
arguments: [ !service { class: Anonymous } ]
9+
10+
# Ensure next conditionals are not considered as services
11+
Bar:
12+
autowire: true
13+
14+
Foo: ~
+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
+80Lines 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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.