8000 New Component: Expression Language by fabpot · Pull Request #8913 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

New Component: Expression Language #8913

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Sep 19, 2013
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[DependencyInjection] added support for expressions in the service co…
…ntainer
  • Loading branch information
fabpot committed Sep 19, 2013
commit c25abd9c72ff94c72a4dc57fba3e9572b4818aca
1 change: 1 addition & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 24 additions & 2 deletions src/Symfony/Component/DependencyInjection/ContainerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
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.
Expand Down Expand Up @@ -78,6 +80,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
*/
private $proxyInstantiator;

/**
* @var ExpressionLanguage|null
*/
private $expressionLanguage;

/**
* Sets the track resources flag.
*
Expand Down Expand Up @@ -983,11 +990,12 @@ public function createService(Definition $definition, $id, $tryProxy = true)
}

/**
* 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)
{
Expand All @@ -999,6 +1007,8 @@ public function resolveServices($value)
$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;
Expand Down Expand Up @@ -1149,4 +1159,16 @@ private function shareService(Definition $definition, $service, $id)
}
}
}

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 changes: 17 additions & 0 deletions src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1197,6 +1200,8 @@ private function dumpValue($value, $interpolate = true)
}

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)) {
Expand Down Expand Up @@ -1319,4 +1324,16 @@ private function getNextVariableName()
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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.
Expand Down Expand Up @@ -259,6 +260,10 @@ private function convertParameters($parameters, $type, \DOMElement $parent, $key
} 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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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.
Expand Down Expand Up @@ -231,6 +232,8 @@ private function dumpValue($value)
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.');
}
Expand Down Expand Up @@ -267,6 +270,11 @@ private function getParameterCall($id)
return sprintf('%%%s%%', $id);
}

private function getExpressionCall($expression)
{
return sprintf('@=%s', $expression);
}

/**
* Prepares parameters.
*
Expand Down
42 changes: 42 additions & 0 deletions src/Symfony/Component/DependencyInjection/ExpressionLanguage.php
Original file line number Diff line number Diff line change
@@ -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);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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.
Expand Down Expand Up @@ -311,6 +312,8 @@ private function resolveServices($value)
{
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection;

use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\ExpressionLanguage\Expression;

/**
* SimpleXMLElement class.
Expand Down Expand Up @@ -77,6 +78,9 @@ public function getArgumentsAsPhp($name, $lowercase = true)

$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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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
{
Expand Down Expand Up @@ -377,6 +378,15 @@ public function testCreateSyntheticService()
$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
*/
Expand All @@ -386,6 +396,7 @@ public function testResolveServices()
$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');
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\ExpressionLanguage\Expression;

$container = new ContainerBuilder();
$container->
Expand Down Expand Up @@ -50,7 +51,8 @@
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')->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ function sc_configure($instance)
class BarClass
{
protected $baz;
public $foo = 'foo';

public function setBaz(BazClass $baz)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ protected function getMethodCall1Service()
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ protected function getMethodCall1Service()

$instance->setBar($this->get('foo'));
$instance->setBar(NULL);
$instance->setBar(($this->get("foo")->foo() . $this->getParameter("foo")));

return $instance;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ services:
calls:
- [ setBar, [] ]
- [ setBar ]
- [ setBar, ['@=service("foo").foo() ~ parameter("foo")'] ]
method_call2:
class: FooClass
calls:
Expand Down
10000
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ services:
- [setBar, ['@?foo2']]
- [setBar, ['@?foo3']]
- [setBar, ['@?foobaz']]
- [setBar, ['@=service("foo").foo() ~ parameter("foo")']]

factory_service:
class: Bar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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
{
Expand Down Expand Up @@ -211,7 +212,7 @@ public function testLoadServices()
$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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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
{
Expand Down Expand Up @@ -113,7 +114,7 @@ public function testLoadServices()
$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());

Expand Down
Loading
0