From 685ff0e2804a2e197ca7d62334f270088d30089d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 10 Aug 2017 13:45:16 +0200 Subject: [PATCH 1/5] [DI] Fix YamlDumper not dumping abstract and autoconfigure --- .../DependencyInjection/Dumper/YamlDumper.php | 8 ++++++++ .../Tests/Dumper/YamlDumperTest.php | 12 ++++++++++++ .../Tests/Fixtures/yaml/services_dump_load.yml | 14 ++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php index e71df2af24c14..9567fee56a583 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php @@ -116,6 +116,14 @@ private function addService($id, $definition) $code .= sprintf(" autowiring_types:\n%s", $autowiringTypesCode); } + if ($definition->isAutoconfigured()) { + $code .= " autoconfigure: true\n"; + } + + if ($definition->isAbstract()) { + $code .= " abstract: true\n"; + } + if ($definition->isLazy()) { $code .= " lazy: true\n"; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php index 22277c7b7a85f..eaaad0c75ca58 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php @@ -12,8 +12,10 @@ namespace Symfony\Component\DependencyInjection\Tests\Dumper; use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\YamlDumper; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Parser; @@ -64,6 +66,16 @@ public function testDumpAutowireData() $this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services24.yml', $dumper->dump()); } + public function testDumpLoad() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); + $loader->load('services_dump_load.yml'); + + $dumper = new YamlDumper($container); + $this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services_dump_load.yml', $dumper->dump()); + } + private function assertEqualYamlStructure($expected, $yaml, $message = '') { $parser = new Parser(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml new file mode 100644 index 0000000000000..43b0c7d58a00f --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml @@ -0,0 +1,14 @@ + +services: + service_container: + class: Symfony\Component\DependencyInjection\ContainerInterface + synthetic: true + foo: + autoconfigure: true + abstract: true + Psr\Container\ContainerInterface: + alias: service_container + public: false + Symfony\Component\DependencyInjection\ContainerInterface: + alias: service_container + public: false From 676012748ab5be2f5a510ee9cfce0bfc03828a80 Mon Sep 17 00:00:00 2001 From: ElectricMaxxx Date: Thu, 10 Aug 2017 09:29:20 +0200 Subject: [PATCH 2/5] restrict reflection doc block The version 3.2.0 and 3.2.1 of reflection-docblock is broken and lower version as 3.1 miss some tags --- composer.json | 2 +- .../Tests/Extractors/PhpDocExtractorTest.php | 1 + .../Extractors/ReflectionExtractorTest.php | 81 ++++++++++++------- .../PropertyInfo/Tests/Fixtures/Dummy.php | 7 ++ .../Component/PropertyInfo/composer.json | 2 +- 5 files changed, 64 insertions(+), 29 deletions(-) diff --git a/composer.json b/composer.json index bf18c7b79995f..00faa0845d422 100644 --- a/composer.json +++ b/composer.json @@ -102,7 +102,7 @@ "sensio/framework-extra-bundle": "^3.0.2" }, "conflict": { - "phpdocumentor/reflection-docblock": "<3.0||>=3.2.0,<3.2.1", + "phpdocumentor/reflection-docblock": "<3.0||>=3.2.0,<3.2.2", "phpdocumentor/type-resolver": "<0.2.0", "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" }, diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php index d0eb3eed06c49..f41056050870c 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php @@ -72,6 +72,7 @@ public function typesProvider() array('donotexist', null, null, null), array('staticGetter', null, null, null), array('staticSetter', null, null, null), + array('emptyVar', null, null, null), ); } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php index 573528c012f55..905b50d5dff86 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php @@ -41,6 +41,7 @@ public function testGetProperties() 'B', 'Guid', 'g', + 'emptyVar', 'foo', 'foo2', 'foo3', @@ -122,37 +123,63 @@ public function php71TypesProvider() ); } - public function testIsReadable() + /** + * @dataProvider getReadableProperties + */ + public function testIsReadable($property, $expected) + { + $this->assertSame( + $expected, + $this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property, array()) + ); + } + + public function getReadableProperties() + { + return array( + array('bar', false), + array('baz', false), + array('parent', true), + array('a', true), + array('b', false), + array('c', true), + array('d', true), + array('e', false), + array('f', false), + array('Id', true), + array('id', true), + array('Guid', true), + array('guid', false), + ); + } + + /** + * @dataProvider getWritableProperties + */ + public function testIsWritable($property, $expected) { - $this->assertFalse($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'bar', array())); - $this->assertFalse($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'baz', array())); - $this->assertTrue($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'parent', array())); - $this->assertTrue($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'a', array())); - $this->assertFalse($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'b', array())); - $this->assertTrue($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'c', array())); - $this->assertTrue($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'd', array())); - $this->assertFalse($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'e', array())); - $this->assertFalse($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'f', array())); - $this->assertTrue($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'Id', array())); - $this->assertTrue($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'id', array())); - $this->assertTrue($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'Guid', array())); - $this->assertFalse($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'guid', array())); + $this->assertSame( + $expected, + $this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property, array()) + ); } - public function testIsWritable() + public function getWritableProperties() { - $this->assertFalse($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'bar', array())); - $this->assertFalse($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'baz', array())); - $this->assertTrue($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'parent', array())); - $this->assertFalse($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'a', array())); - $this->assertTrue($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'b', array())); - $this->assertFalse($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'c', array())); - $this->assertFalse($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'd', array())); - $this->assertTrue($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'e', array())); - $this->assertTrue($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'f', array())); - $this->assertFalse($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'Id', array())); - $this->assertTrue($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'Guid', array())); - $this->assertFalse($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'guid', array())); + return array( + array('bar', false), + array('baz', false), + array('parent', true), + array('a', false), + array('b', true), + array('c', false), + array('d', false), + array('e', true), + array('f', true), + array('Id', false), + array('Guid', true), + array('guid', false), + ); } public function testSingularize() diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php index d358bae13ad61..4e558eca014e5 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php @@ -68,6 +68,13 @@ class Dummy extends ParentDummy */ public $g; + /** + * This should not be removed. + * + * @var + */ + public $emptyVar; + public static function getStatic() { } diff --git a/src/Symfony/Component/PropertyInfo/composer.json b/src/Symfony/Component/PropertyInfo/composer.json index 20bdccf00156b..13aebfbf0e31e 100644 --- a/src/Symfony/Component/PropertyInfo/composer.json +++ b/src/Symfony/Component/PropertyInfo/composer.json @@ -34,7 +34,7 @@ "doctrine/annotations": "~1.0" }, "conflict": { - "phpdocumentor/reflection-docblock": "<3.0||>=3.2.0,<3.2.1", + "phpdocumentor/reflection-docblock": "<3.0||>=3.2.0,<3.2.2", "phpdocumentor/type-resolver": "<0.2.0", "symfony/dependency-injection": "<3.3" }, From c396e8cb9cfa2c5fdd46f4d6c821cd1c462c4cbb Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 10 Aug 2017 16:37:13 +0200 Subject: [PATCH 3/5] [DI] Fix dumping abstract with YamlDumper --- .../DependencyInjection/Dumper/YamlDumper.php | 4 ++++ .../Tests/Dumper/YamlDumperTest.php | 12 ++++++++++++ .../Tests/Fixtures/yaml/services_dump_load.yml | 4 ++++ 3 files changed, 20 insertions(+) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php index 810e7539fe1bd..9125a97836b6f 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php @@ -104,6 +104,10 @@ private function addService($id, $definition) $code .= sprintf(" factory_class: %s\n", $this->dumper->dump($definition->getFactoryClass(false))); } + if ($definition->isAbstract()) { + $code .= " abstract: true\n"; + } + if ($definition->isLazy()) { $code .= " lazy: true\n"; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php index f19a2f5cb8346..81bbd5316c444 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/YamlDumperTest.php @@ -12,8 +12,10 @@ namespace Symfony\Component\DependencyInjection\Tests\Dumper; use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\YamlDumper; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\Yaml\Yaml; class YamlDumperTest extends TestCase @@ -77,6 +79,16 @@ public function testAddService() } } + public function testDumpLoad() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); + $loader->load('services_dump_load.yml'); + + $dumper = new YamlDumper($container); + $this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services_dump_load.yml', $dumper->dump()); + } + private function assertEqualYamlStructure($yaml, $expected, $message = '') { $this->assertEquals(Yaml::parse($expected), Yaml::parse($yaml), $message); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml new file mode 100644 index 0000000000000..bcf8f31b36115 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services_dump_load.yml @@ -0,0 +1,4 @@ + +services: + foo: + abstract: true From eeaea83d380abd190a999b3addce784ca177ca7d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 10 Aug 2017 21:55:15 +0200 Subject: [PATCH 4/5] fix merge --- .../PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php index d3a851d58850e..02729b4bf90e4 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php @@ -73,6 +73,7 @@ public function testGetPropertiesWithCustomPrefixes() 'B', 'Guid', 'g', + 'emptyVar', 'foo', 'foo2', 'foo3', @@ -100,6 +101,7 @@ public function testGetPropertiesWithNoPrefixes() 'B', 'Guid', 'g', + 'emptyVar', 'foo', 'foo2', 'foo3', From 2a84059837838e24516495c1463521b9db49b7f8 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 22 Oct 2016 18:25:15 +0200 Subject: [PATCH 5/5] [DI] Allow fetching env vars with lookup-dedicated services --- .../CheckEnvReferencedServicesPass.php | 45 ++++++++++++++ .../Compiler/InlineServiceDefinitionsPass.php | 5 ++ .../Compiler/PassConfig.php | 4 ++ .../Compiler/RemoveUnusedDefinitionsPass.php | 4 +- .../DependencyInjection/Container.php | 10 ++- .../DependencyInjection/Dumper/PhpDumper.php | 2 +- .../DependencyInjection/GetEnvInterface.php | 29 +++++++++ .../EnvPlaceholderParameterBag.php | 18 +++++- .../CheckEnvReferencedServicesPassTest.php | 62 +++++++++++++++++++ .../Tests/Dumper/PhpDumperTest.php | 15 ++++- .../Tests/Fixtures/php/services26.php | 24 ++++++- .../Tests/Fixtures/yaml/services26.yml | 4 ++ .../EnvPlaceholderParameterBagTest.php | 22 +++++++ 13 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Compiler/CheckEnvReferencedServicesPass.php create mode 100644 src/Symfony/Component/DependencyInjection/GetEnvInterface.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckEnvReferencedServicesPassTest.php diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckEnvReferencedServicesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckEnvReferencedServicesPass.php new file mode 100644 index 0000000000000..0ad0e22fe202c --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckEnvReferencedServicesPass.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\GetEnvInterface; +use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; + +/** + * Checks that all env-referenced services exist and implement GetEnvInterface. + * + * @author Nicolas Grekas + */ +class CheckEnvReferencedServicesPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + $envReferencedServices = $container->getParameterBag() instanceof EnvPlaceholderParameterBag ? $container->getParameterBag()->getEnvReferencedServices() : array(); + + foreach ($envReferencedServices as $id) { + if (!$container->has($id)) { + throw new ServiceNotFoundException($id); + } + $class = $container->getDefinition($id)->getClass(); + if (!is_subclass_of($class, GetEnvInterface::class)) { + if (!class_exists($class, false)) { + throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + + throw new InvalidArgumentException(sprintf('The service "%s" referenced in env parameters must implement "%s".', $id, GetEnvInterface::class)); + } + } + } +} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index f2ef363c837cc..1d21b1d7713d7 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -13,6 +13,7 @@ use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\DependencyInjection\Reference; /** @@ -107,6 +108,10 @@ private function isInlineableDefinition($id, Definition $definition, ServiceRefe return false; } + if (($bag = $this->container->getParameterBag)() instanceof EnvPlaceholderParameterBag && isset($bag->getEnvReferencedServices()[$id])) { + return false; + } + return true; } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index 184c7b25a0883..9d36d83fcbc0d 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -81,6 +81,10 @@ public function __construct() new AutowireExceptionPass($autowirePass, $inlinedServicePass), new CheckExceptionOnInvalidReferenceBehaviorPass(), )); + + $this->afterRemovingPasses = array(array( + new CheckEnvReferencedServicesPass(), + )); } /** diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php index 79a2600d8f785..8b47822c93443 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; /** * Removes unused service definitions from the container. @@ -38,10 +39,11 @@ public function setRepeatedPass(RepeatedPass $repeatedPass) public function process(ContainerBuilder $container) { $graph = $container->getCompiler()->getServiceReferenceGraph(); + $envReferencedServices = $container->getParameterBag() instanceof EnvPlaceholderParameterBag ? $container->getParameterBag()->getEnvReferencedServices() : array(); $hasChanged = false; foreach ($container->getDefinitions() as $id => $definition) { - if ($definition->isPublic()) { + if ($definition->isPublic() || isset($envReferencedServices[$id])) { continue; } diff --git a/src/Symfony/Component/DependencyInjection/Container.php b/src/Symfony/Component/DependencyInjection/Container.php index 187571e9765dd..af6891ab448fe 100644 --- a/src/Symfony/Component/DependencyInjection/Container.php +++ b/src/Symfony/Component/DependencyInjection/Container.php @@ -443,7 +443,7 @@ protected function load($file) * * @param string The name of the environment variable * - * @return scalar The value to use for the provided environment variable name + * @return mixed The value to use for the provided environment variable * * @throws EnvNotFoundException When the environment variable is not found and has no default value */ @@ -458,6 +458,14 @@ protected function getEnv($name) if (false !== $env = getenv($name)) { return $this->envCache[$name] = $env; } + if (false !== $i = strpos($name, '@')) { + $id = substr($name, 1 + $i); + $service = isset($this->services[$id]) ? $this->services[$id] : (isset($this->methodMap[$id]) ? $this->{$this->methodMap[$id]}() : $this->get($id)); + + if (null !== $env = $service->getEnv(substr($name, 0, $i))) { + return $this->envCache[$name] = $env; + } + } if (!$this->hasParameter("env($name)")) { throw new EnvNotFoundException($name); } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index cdcea02ed19d0..c80ea401800fd 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -1064,7 +1064,7 @@ private function addDefaultParametersMethod() $export = $this->exportParameters(array($value)); $export = explode('0 => ', substr(rtrim($export, " )\n"), 7, -1), 2); - if (preg_match("/\\\$this->(?:getEnv\('\w++'\)|targetDirs\[\d++\])/", $export[1])) { + if (preg_match("/\\\$this->(?:getEnv\('[-.a-zA-Z0-9_\x7f-\xff]++(?:@[-.a-zA-Z0-9_\x7f-\xff]++)?'\)|targetDirs\[\d++\])/", $export[1])) { $dynamicPhp[$key] = sprintf('%scase %s: $value = %s; break;', $export[0], $this->export($key), $export[1]); } else { $php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]); diff --git a/src/Symfony/Component/DependencyInjection/GetEnvInterface.php b/src/Symfony/Component/DependencyInjection/GetEnvInterface.php new file mode 100644 index 0000000000000..aca1afd2a8fb0 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/GetEnvInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * The GetEnvInterface is implemented by objects that manage environment-like variables. + * + * @author Nicolas Grekas + */ +interface GetEnvInterface +{ + /** + * Returns the value of the given variable as managed by the current instance. + * + * @param string $name The name of the variable + * + * @return mixed|null The value of the given variable or null when it is not found + */ + public function getEnv($name); +} diff --git a/src/Symfony/Component/DependencyInjection/ParameterBag/EnvPlaceholderParameterBag.php b/src/Symfony/Component/DependencyInjection/ParameterBag/EnvPlaceholderParameterBag.php index d20e53531aa3b..3988068374a5b 100644 --- a/src/Symfony/Component/DependencyInjection/ParameterBag/EnvPlaceholderParameterBag.php +++ b/src/Symfony/Component/DependencyInjection/ParameterBag/EnvPlaceholderParameterBag.php @@ -20,6 +20,7 @@ class EnvPlaceholderParameterBag extends ParameterBag { private $envPlaceholders = array(); + private $envReferencedServices = array(); /** * {@inheritdoc} @@ -34,7 +35,7 @@ public function get($name) return $placeholder; // return first result } } - if (preg_match('/\W/', $env)) { + if (!preg_match('/^([-.a-zA-Z0-9_\x7f-\xff]++)(?:@([-.a-zA-Z0-9_\x7f-\xff]++))?$/', $env, $match)) { throw new InvalidArgumentException(sprintf('Invalid %s name: only "word" characters are allowed.', $name)); } @@ -45,6 +46,11 @@ public function get($name) throw new RuntimeException(sprintf('The default value of an env() parameter must be scalar or null, but "%s" given to "%s".', gettype($defaultValue), $name)); } } + if (isset($match[2])) { + $serviceId = strtolower($match[2]); + $this->envReferencedServices[$serviceId] = $serviceId; + $env = $match[1].'@'.$serviceId; + } $uniqueName = md5($name.uniqid(mt_rand(), true)); $placeholder = sprintf('env_%s_%s', $env, $uniqueName); @@ -66,6 +72,16 @@ public function getEnvPlaceholders() return $this->envPlaceholders; } + /** + * Returns the list of services referenced in `env()` parameters. + * + * @return string[] The list of services referenced in `env()` parameters + */ + public function getEnvReferencedServices() + { + return $this->envReferencedServices; + } + /** * Merges the env placeholders of another EnvPlaceholderParameterBag. */ diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckEnvReferencedServicesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckEnvReferencedServicesPassTest.php new file mode 100644 index 0000000000000..52c268b22d44e --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckEnvReferencedServicesPassTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CheckEnvReferencedServicesPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\GetEnvInterface; + +class CheckEnvReferencedServicePassTest extends \PHPUnit_Framework_TestCase +{ + public function testProcess() + { + $container = new ContainerBuilder(); + $container->getParameterBag()->get('env(foo@a)'); + $container->register('a', GetEnvService::class); + + $pass = new CheckEnvReferencedServicesPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException + */ + public function testProcessThrowsExceptionOnInvalidReference() + { + $container = new ContainerBuilder(); + $container->getParameterBag()->get('env(foo@a)'); + + $pass = new CheckEnvReferencedServicesPass(); + $pass->process($container); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException + */ + public function testProcessThrowsExceptionOnInvalidReferenceFromInlinedDefinition() + { + $container = new ContainerBuilder(); + $container->getParameterBag()->get('env(foo@a)'); + $container->register('a', \stdClass::class); + + $pass = new CheckEnvReferencedServicesPass(); + $pass->process($container); + } +} + +class GetEnvService implements GetEnvInterface +{ + public function getEnv($name) + { + return $name.$name; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index fc5a950af4709..8112283386def 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -21,6 +21,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\DependencyInjection\GetEnvInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator; @@ -344,7 +345,11 @@ public function testEnvParameter() $container->compile(); $dumper = new PhpDumper($container); - $this->assertStringEqualsFile(self::$fixturesPath.'/php/services26.php', $dumper->dump(), '->dump() dumps inline definitions which reference service_container'); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services26.php', $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_EnvParameters')), '->dump() dumps inline definitions which reference service_container'); + + require self::$fixturesPath.'/php/services26.php'; + $container = new \Symfony_DI_PhpDumper_Test_EnvParameters(); + $this->assertSame('BAZ123', $container->getParameter('baz')); } /** @@ -671,3 +676,11 @@ public function testPrivateServiceTriggersDeprecation() $container->get('bar'); } } + +class EnvResolver implements GetEnvInterface +{ + public function getEnv($name) + { + return strtoupper($name).'123'; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php index 5ec14c1026268..8ac302905c2c2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php @@ -9,14 +9,14 @@ use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; /** - * ProjectServiceContainer. + * Symfony_DI_PhpDumper_Test_EnvParameters. * * This class has been auto-generated * by the Symfony Dependency Injection Component. * * @final since Symfony 3.3 */ -class ProjectServiceContainer extends Container +class Symfony_DI_PhpDumper_Test_EnvParameters extends Container { private $parameters; private $targetDirs = array(); @@ -30,6 +30,7 @@ public function __construct() $this->services = array(); $this->methodMap = array( + 'env_resolver' => 'getEnvResolverService', 'test' => 'getTestService', ); @@ -74,6 +75,23 @@ protected function getTestService() return $this->services['test'] = new $class($this->getEnv('Bar'), 'foo'.$this->getEnv('FOO').'baz'); } + /** + * Gets the 'env_resolver' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * This service is private. + * If you want to be able to request this service from the container directly, + * make it public, otherwise you might end up with broken code. + * + * @return \Symfony\Component\DependencyInjection\Tests\Dumper\EnvResolver A Symfony\Component\DependencyInjection\Tests\Dumper\EnvResolver instance + */ + protected function getEnvResolverService() + { + return $this->services['env_resolver'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\EnvResolver(); + } + /** * {@inheritdoc} */ @@ -129,6 +147,7 @@ public function getParameterBag() private $loadedDynamicParameters = array( 'bar' => false, + 'baz' => false, ); private $dynamicParameters = array(); @@ -145,6 +164,7 @@ private function getDynamicParameter($name) { switch ($name) { case 'bar': $value = $this->getEnv('FOO'); break; + case 'baz': $value = $this->getEnv('Baz@env_resolver'); break; default: throw new InvalidArgumentException(sprintf('The dynamic parameter "%s" must be defined.', $name)); } $this->loadedDynamicParameters[$name] = true; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services26.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services26.yml index 2ef23c1af545f..4c9875d46bdcb 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services26.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services26.yml @@ -1,6 +1,7 @@ parameters: env(FOO): foo bar: '%env(FOO)%' + baz: '%env(Baz@Env_Resolver)%' services: test: @@ -8,3 +9,6 @@ services: arguments: - '%env(Bar)%' - 'foo%bar%baz' + env_resolver: + class: 'Symfony\Component\DependencyInjection\Tests\Dumper\EnvResolver' + public: false diff --git a/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/EnvPlaceholderParameterBagTest.php b/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/EnvPlaceholderParameterBagTest.php index 01fcd2c3ef10b..8415ed63e0f8c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/EnvPlaceholderParameterBagTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ParameterBag/EnvPlaceholderParameterBagTest.php @@ -162,4 +162,26 @@ public function testGetThrowsOnBadDefaultValue() $bag->get('env(ARRAY_VAR)'); $bag->resolve(); } + + public function testEnvReferencedServices() + { + $bag = new EnvPlaceholderParameterBag(); + $bag->get('env(foo@SERVICE)'); + + $this->assertSame(array('service' => 'service'), $bag->getEnvReferencedServices()); + + $expected = <<<'EOTXT' +Array +( + [foo@service] => Array + ( + [env_foo@service_%s] => env_foo@service_%s + ) + +) + +EOTXT; + + $this->assertStringMatchesFormat($expected, print_r($bag->getEnvPlaceholders(), true)); + } }