[DependencyInjection] added support for expressions in the service container
This commit is contained in:
parent
3a41781640
commit
c25abd9c72
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* 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 <fabien@symfony.com>
|
||||
*/
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -164,6 +164,7 @@
|
|||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="collection" />
|
||||
<xsd:enumeration value="service" />
|
||||
<xsd:enumeration value="expression" />
|
||||
<xsd:enumeration value="string" />
|
||||
<xsd:enumeration value="constant" />
|
||||
</xsd:restriction>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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')->
|
||||
|
|
|
@ -8,6 +8,7 @@ function sc_configure($instance)
|
|||
class BarClass
|
||||
{
|
||||
protected $baz;
|
||||
public $foo = 'foo';
|
||||
|
||||
public function setBaz(BazClass $baz)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,9 @@
|
|||
</service>
|
||||
<service id="method_call1" class="FooClass">
|
||||
<call method="setBar" />
|
||||
<call method="setBar">
|
||||
<argument type="expression">service("foo").foo() ~ parameter("foo")</argument>
|
||||
</call>
|
||||
</service>
|
||||
<service id="method_call2" class="FooClass">
|
||||
<call method="setBar">
|
||||
|
|
|
@ -49,6 +49,9 @@
|
|||
<call method="setBar">
|
||||
<argument type="service" id="foobaz" on-invalid="ignore"/>
|
||||
</call>
|
||||
<call method="setBar">
|
||||
<argument type="expression">service("foo").foo() ~ parameter("foo")</argument>
|
||||
</call>
|
||||
</service>
|
||||
<service id="factory_service" class="Bar" factory-method="getInstance" factory-service="foo.baz"/>
|
||||
<service id="foo_with_inline" class="Foo">
|
||||
|
|
|
@ -15,6 +15,7 @@ services:
|
|||
calls:
|
||||
- [ setBar, [] ]
|
||||
- [ setBar ]
|
||||
- [ setBar, ['@=service("foo").foo() ~ parameter("foo")'] ]
|
||||
method_call2:
|
||||
class: FooClass
|
||||
calls:
|
||||
|
|
|
@ -38,6 +38,7 @@ services:
|
|||
- [setBar, ['@?foo2']]
|
||||
- [setBar, ['@?foo3']]
|
||||
- [setBar, ['@?foobaz']]
|
||||
- [setBar, ['@=service("foo").foo() ~ parameter("foo")']]
|
||||
|
||||
factory_service:
|
||||
class: Bar
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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": "",
|
||||
|
|
Reference in New Issue