diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index d3ca34c2bc..9a41e85705 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 2.4.0 ----- + * added support for expressions in service definitions * added ContainerAwareTrait to add default container aware behavior to a class 2.2.0 diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 46d908c551..8085975e2f 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -24,6 +24,8 @@ use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator; +use Symfony\Component\DependencyInjection\ExpressionLanguage; +use Symfony\Component\ExpressionLanguage\Expression; /** * ContainerBuilder is a DI container that provides an API to easily describe services. @@ -78,6 +80,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface */ private $proxyInstantiator; + /** + * @var ExpressionLanguage|null + */ + private $expressionLanguage; + /** * Sets the track resources flag. * @@ -983,11 +990,12 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } /** - * Replaces service references by the real service instance. + * Replaces service references by the real service instance and evaluates expressions. * * @param mixed $value A value * - * @return mixed The same value with all service references replaced by the real service instances + * @return mixed The same value with all service references replaced by + * the real service instances and all expressions evaluated */ public function resolveServices($value) { @@ -999,6 +1007,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface $value = $this->get((string) $value, $value->getInvalidBehavior()); } elseif ($value instanceof Definition) { $value = $this->createService($value, null); + } elseif ($value instanceof Expression) { + $value = $this->getExpressionLanguage()->evaluate($value, array('this' => $this)); } return $value; @@ -1149,4 +1159,16 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } } } + + private function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(); + } + + return $this->expressionLanguage; + } } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index e92960e21e..e55507f751 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -23,6 +23,8 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper; +use Symfony\Component\DependencyInjection\ExpressionLanguage; +use Symfony\Component\ExpressionLanguage\Expression; /** * PhpDumper dumps a service container as a PHP class. @@ -51,6 +53,7 @@ class PhpDumper extends Dumper private $referenceVariables; private $variableCount; private $reservedVariables = array('instance', 'class'); + private $expressionLanguage; /** * @var \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface @@ -1197,6 +1200,8 @@ EOF; } return $this->getServiceCall((string) $value, $value); + } elseif ($value instanceof Expression) { + return $this->getExpressionLanguage()->compile((string) $value, array('this')); } elseif ($value instanceof Parameter) { return $this->dumpParameter($value); } elseif (true === $interpolate && is_string($value)) { @@ -1319,4 +1324,16 @@ EOF; return $name; } } + + private function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(); + } + + return $this->expressionLanguage; + } } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php index a311af348e..31bec31b22 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\ExpressionLanguage\Expression; /** * XmlDumper dumps a service container as an XML string. @@ -259,6 +260,10 @@ class XmlDumper extends Dumper } elseif ($value instanceof Definition) { $element->setAttribute('type', 'service'); $this->addService($value, null, $element); + } elseif ($value instanceof Expression) { + $element->setAttribute('type', 'expression'); + $text = $this->document->createTextNode(self::phpToXml((string) $value)); + $element->appendChild($text); } else { if (in_array($value, array('null', 'true', 'false'), true)) { $element->setAttribute('type', 'string'); diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php index 807e656283..4d72e86753 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php @@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\ExpressionLanguage\Expression; /** * YamlDumper dumps a service container as a YAML string. @@ -231,6 +232,8 @@ class YamlDumper extends Dumper return $this->getServiceCall((string) $value, $value); } elseif ($value instanceof Parameter) { return $this->getParameterCall((string) $value); + } elseif ($value instanceof Expression) { + return $this->getExpressionCall((string) $value); } elseif (is_object($value) || is_resource($value)) { throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); } @@ -267,6 +270,11 @@ class YamlDumper extends Dumper return sprintf('%%%s%%', $id); } + private function getExpressionCall($expression) + { + return sprintf('@=%s', $expression); + } + /** * Prepares parameters. * diff --git a/src/Symfony/Component/DependencyInjection/ExpressionLanguage.php b/src/Symfony/Component/DependencyInjection/ExpressionLanguage.php new file mode 100644 index 0000000000..5b97b202b7 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/ExpressionLanguage.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage; + +/** + * Adds some function to the default ExpressionLanguage. + * + * To get a service, use service('request'). + * To get a parameter, use parameter('kernel.debug'). + * + * @author Fabien Potencier + */ +class ExpressionLanguage extends BaseExpressionLanguage +{ + protected function registerFunctions() + { + parent::registerFunctions(); + + $this->addFunction('service', function ($arg) { + return sprintf('$this->get(%s)', $arg); + }, function (array $variables, $value) { + return $variables['container']->get($value); + }); + + $this->addFunction('parameter', function ($arg) { + return sprintf('$this->getParameter(%s)', $arg); + }, function (array $variables, $value) { + return $variables['container']->getParameter($value); + }); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index ce135fa81f..28e5a026d4 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\ExpressionLanguage\Expression; /** * YamlFileLoader loads YAML files service definitions. @@ -311,6 +312,8 @@ class YamlFileLoader extends FileLoader { if (is_array($value)) { $value = array_map(array($this, 'resolveServices'), $value); + } elseif (is_string($value) && 0 === strpos($value, '@=')) { + return new Expression(substr($value, 2)); } elseif (is_string($value) && 0 === strpos($value, '@')) { if (0 === strpos($value, '@@')) { $value = substr($value, 1); diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index f1c2003c62..e00bbc23c6 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -164,6 +164,7 @@ + diff --git a/src/Symfony/Component/DependencyInjection/SimpleXMLElement.php b/src/Symfony/Component/DependencyInjection/SimpleXMLElement.php index cc5e311987..2d995e2dd0 100644 --- a/src/Symfony/Component/DependencyInjection/SimpleXMLElement.php +++ b/src/Symfony/Component/DependencyInjection/SimpleXMLElement.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection; use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\ExpressionLanguage\Expression; /** * SimpleXMLElement class. @@ -77,6 +78,9 @@ class SimpleXMLElement extends \SimpleXMLElement $arguments[$key] = new Reference((string) $arg['id'], $invalidBehavior, $strict); break; + case 'expression': + $arguments[$key] = new Expression((string) $arg); + break; case 'collection': $arguments[$key] = $arg->getArgumentsAsPhp($name, false); break; diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index ff64821e23..87693bff00 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -25,6 +25,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Scope; use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\ExpressionLanguage\Expression; class ContainerBuilderTest extends \PHPUnit_Framework_TestCase { @@ -377,6 +378,15 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $builder->get('foo'); } + public function testCreateServiceWithExpression() + { + $builder = new ContainerBuilder(); + $builder->setParameter('bar', 'bar'); + $builder->register('bar', 'BarClass'); + $builder->register('foo', 'FooClass')->addArgument(array('foo' => new Expression('service("bar").foo ~ parameter("bar")'))); + $this->assertEquals('foobar', $builder->get('foo')->arguments['foo']); + } + /** * @covers Symfony\Component\DependencyInjection\ContainerBuilder::resolveServices */ @@ -386,6 +396,7 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $builder->register('foo', 'FooClass'); $this->assertEquals($builder->get('foo'), $builder->resolveServices(new Reference('foo')), '->resolveServices() resolves service references to service instances'); $this->assertEquals(array('foo' => array('foo', $builder->get('foo'))), $builder->resolveServices(array('foo' => array('foo', new Reference('foo')))), '->resolveServices() resolves service references to service instances in nested arrays'); + $this->assertEquals($builder->get('foo'), $builder->resolveServices(new Expression('service("foo")')), '->resolveServices() resolves expressions'); } /** diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php index 6abe5e2a91..b5e9b07308 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php @@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\ExpressionLanguage\Expression; $container = new ContainerBuilder(); $container-> @@ -50,7 +51,8 @@ $container-> addMethodCall('setBar', array(new Reference('foo')))-> addMethodCall('setBar', array(new Reference('foo2', ContainerInterface::NULL_ON_INVALID_REFERENCE)))-> addMethodCall('setBar', array(new Reference('foo3', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)))-> - addMethodCall('setBar', array(new Reference('foobaz', ContainerInterface::IGNORE_ON_INVALID_REFERENCE))) + addMethodCall('setBar', array(new Reference('foobaz', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)))-> + addMethodCall('setBar', array(new Expression('service("foo").foo() ~ parameter("foo")'))) ; $container-> register('factory_service', 'Bar')-> diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php index 48e1c2c84e..70c3d275b8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php @@ -8,6 +8,7 @@ function sc_configure($instance) class BarClass { protected $baz; + public $foo = 'foo'; public function setBaz(BazClass $baz) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php index 9361a0e87c..97fba3fde2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php @@ -198,6 +198,7 @@ class ProjectServiceContainer extends Container if ($this->has('foobaz')) { $instance->setBar($this->get('foobaz', ContainerInterface::NULL_ON_INVALID_REFERENCE)); } + $instance->setBar(($this->get("foo")->foo() . $this->getParameter("foo"))); return $instance; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php index b60cbff8e8..2004af9aa9 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -203,6 +203,7 @@ class ProjectServiceContainer extends Container $instance->setBar($this->get('foo')); $instance->setBar(NULL); + $instance->setBar(($this->get("foo")->foo() . $this->getParameter("foo"))); return $instance; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services6.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services6.xml index abd9fbc152..ffc038234f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services6.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services6.xml @@ -32,6 +32,9 @@ + + service("foo").foo() ~ parameter("foo") + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml index cb3a1f69de..60fcfce59a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml @@ -49,6 +49,9 @@ + + service("foo").foo() ~ parameter("foo") + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services6.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services6.yml index 7ba9453bdd..d3c793f2e3 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services6.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services6.yml @@ -15,6 +15,7 @@ services: calls: - [ setBar, [] ] - [ setBar ] + - [ setBar, ['@=service("foo").foo() ~ parameter("foo")'] ] method_call2: class: FooClass calls: diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml index 6a4c3d5d97..6744e29f3f 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml @@ -38,6 +38,7 @@ services: - [setBar, ['@?foo2']] - [setBar, ['@?foo3']] - [setBar, ['@?foobaz']] + - [setBar, ['@=service("foo").foo() ~ parameter("foo")']] factory_service: class: Bar diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index e6490ecc35..6d36e2404c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Loader\IniFileLoader; use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\Config\FileLocator; +use Symfony\Component\ExpressionLanguage\Expression; class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase { @@ -211,7 +212,7 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase $this->assertEquals('sc_configure', $services['configurator1']->getConfigurator(), '->load() parses the configurator tag'); $this->assertEquals(array(new Reference('baz', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, false), 'configure'), $services['configurator2']->getConfigurator(), '->load() parses the configurator tag'); $this->assertEquals(array('BazClass', 'configureStatic'), $services['configurator3']->getConfigurator(), '->load() parses the configurator tag'); - $this->assertEquals(array(array('setBar', array())), $services['method_call1']->getMethodCalls(), '->load() parses the method_call tag'); + $this->assertEquals(array(array('setBar', array()), array('setBar', array(new Expression('service("foo").foo() ~ parameter("foo")')))), $services['method_call1']->getMethodCalls(), '->load() parses the method_call tag'); $this->assertEquals(array(array('setBar', array('foo', new Reference('foo'), array(true, false)))), $services['method_call2']->getMethodCalls(), '->load() parses the method_call tag'); $this->assertNull($services['factory_service']->getClass()); $this->assertEquals('getInstance', $services['factory_service']->getFactoryMethod()); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index a567bbe9cd..8287a741b5 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Loader\IniFileLoader; use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\Config\FileLocator; +use Symfony\Component\ExpressionLanguage\Expression; class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase { @@ -113,7 +114,7 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase $this->assertEquals('sc_configure', $services['configurator1']->getConfigurator(), '->load() parses the configurator tag'); $this->assertEquals(array(new Reference('baz'), 'configure'), $services['configurator2']->getConfigurator(), '->load() parses the configurator tag'); $this->assertEquals(array('BazClass', 'configureStatic'), $services['configurator3']->getConfigurator(), '->load() parses the configurator tag'); - $this->assertEquals(array(array('setBar', array()), array('setBar', array())), $services['method_call1']->getMethodCalls(), '->load() parses the method_call tag'); + $this->assertEquals(array(array('setBar', array()), array('setBar', array()), array('setBar', array(new Expression('service("foo").foo() ~ parameter("foo")')))), $services['method_call1']->getMethodCalls(), '->load() parses the method_call tag'); $this->assertEquals(array(array('setBar', array('foo', new Reference('foo'), array(true, false)))), $services['method_call2']->getMethodCalls(), '->load() parses the method_call tag'); $this->assertEquals('baz_factory', $services['factory_service']->getFactoryService()); diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json index 1a3c03dcd2..1a29489d2b 100644 --- a/src/Symfony/Component/DependencyInjection/composer.json +++ b/src/Symfony/Component/DependencyInjection/composer.json @@ -20,7 +20,8 @@ }, "require-dev": { "symfony/yaml": "~2.0", - "symfony/config": "~2.2" + "symfony/config": "~2.2", + "symfony/expression-language": "~2.4" }, "suggest": { "symfony/yaml": "",