[DependencyInjection] added support for expressions in the service container

This commit is contained in:
Fabien Potencier 2013-09-02 12:46:47 +02:00
parent 3a41781640
commit c25abd9c72
21 changed files with 136 additions and 6 deletions

View File

@ -4,6 +4,7 @@ CHANGELOG
2.4.0 2.4.0
----- -----
* added support for expressions in service definitions
* added ContainerAwareTrait to add default container aware behavior to a class * added ContainerAwareTrait to add default container aware behavior to a class
2.2.0 2.2.0

View File

@ -24,6 +24,8 @@ use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\Config\Resource\ResourceInterface;
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator; 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. * 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; private $proxyInstantiator;
/**
* @var ExpressionLanguage|null
*/
private $expressionLanguage;
/** /**
* Sets the track resources flag. * 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 * @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) public function resolveServices($value)
{ {
@ -999,6 +1007,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
$value = $this->get((string) $value, $value->getInvalidBehavior()); $value = $this->get((string) $value, $value->getInvalidBehavior());
} elseif ($value instanceof Definition) { } elseif ($value instanceof Definition) {
$value = $this->createService($value, null); $value = $this->createService($value, null);
} elseif ($value instanceof Expression) {
$value = $this->getExpressionLanguage()->evaluate($value, array('this' => $this));
} }
return $value; 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;
}
} }

View File

@ -23,6 +23,8 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper;
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper; 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. * PhpDumper dumps a service container as a PHP class.
@ -51,6 +53,7 @@ class PhpDumper extends Dumper
private $referenceVariables; private $referenceVariables;
private $variableCount; private $variableCount;
private $reservedVariables = array('instance', 'class'); private $reservedVariables = array('instance', 'class');
private $expressionLanguage;
/** /**
* @var \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface * @var \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface
@ -1197,6 +1200,8 @@ EOF;
} }
return $this->getServiceCall((string) $value, $value); return $this->getServiceCall((string) $value, $value);
} elseif ($value instanceof Expression) {
return $this->getExpressionLanguage()->compile((string) $value, array('this'));
} elseif ($value instanceof Parameter) { } elseif ($value instanceof Parameter) {
return $this->dumpParameter($value); return $this->dumpParameter($value);
} elseif (true === $interpolate && is_string($value)) { } elseif (true === $interpolate && is_string($value)) {
@ -1319,4 +1324,16 @@ EOF;
return $name; 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;
}
} }

View File

@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\ExpressionLanguage\Expression;
/** /**
* XmlDumper dumps a service container as an XML string. * XmlDumper dumps a service container as an XML string.
@ -259,6 +260,10 @@ class XmlDumper extends Dumper
} elseif ($value instanceof Definition) { } elseif ($value instanceof Definition) {
$element->setAttribute('type', 'service'); $element->setAttribute('type', 'service');
$this->addService($value, null, $element); $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 { } else {
if (in_array($value, array('null', 'true', 'false'), true)) { if (in_array($value, array('null', 'true', 'false'), true)) {
$element->setAttribute('type', 'string'); $element->setAttribute('type', 'string');

View File

@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\ExpressionLanguage\Expression;
/** /**
* YamlDumper dumps a service container as a YAML string. * YamlDumper dumps a service container as a YAML string.
@ -231,6 +232,8 @@ class YamlDumper extends Dumper
return $this->getServiceCall((string) $value, $value); return $this->getServiceCall((string) $value, $value);
} elseif ($value instanceof Parameter) { } elseif ($value instanceof Parameter) {
return $this->getParameterCall((string) $value); return $this->getParameterCall((string) $value);
} elseif ($value instanceof Expression) {
return $this->getExpressionCall((string) $value);
} elseif (is_object($value) || is_resource($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.'); 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); return sprintf('%%%s%%', $id);
} }
private function getExpressionCall($expression)
{
return sprintf('@=%s', $expression);
}
/** /**
* Prepares parameters. * Prepares parameters.
* *

View File

@ -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);
});
}
}

View File

@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Yaml\Parser as YamlParser; use Symfony\Component\Yaml\Parser as YamlParser;
use Symfony\Component\ExpressionLanguage\Expression;
/** /**
* YamlFileLoader loads YAML files service definitions. * YamlFileLoader loads YAML files service definitions.
@ -311,6 +312,8 @@ class YamlFileLoader extends FileLoader
{ {
if (is_array($value)) { if (is_array($value)) {
$value = array_map(array($this, 'resolveServices'), $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, '@')) { } elseif (is_string($value) && 0 === strpos($value, '@')) {
if (0 === strpos($value, '@@')) { if (0 === strpos($value, '@@')) {
$value = substr($value, 1); $value = substr($value, 1);

View File

@ -164,6 +164,7 @@
<xsd:restriction base="xsd:string"> <xsd:restriction base="xsd:string">
<xsd:enumeration value="collection" /> <xsd:enumeration value="collection" />
<xsd:enumeration value="service" /> <xsd:enumeration value="service" />
<xsd:enumeration value="expression" />
<xsd:enumeration value="string" /> <xsd:enumeration value="string" />
<xsd:enumeration value="constant" /> <xsd:enumeration value="constant" />
</xsd:restriction> </xsd:restriction>

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection; namespace Symfony\Component\DependencyInjection;
use Symfony\Component\Config\Util\XmlUtils; use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\ExpressionLanguage\Expression;
/** /**
* SimpleXMLElement class. * SimpleXMLElement class.
@ -77,6 +78,9 @@ class SimpleXMLElement extends \SimpleXMLElement
$arguments[$key] = new Reference((string) $arg['id'], $invalidBehavior, $strict); $arguments[$key] = new Reference((string) $arg['id'], $invalidBehavior, $strict);
break; break;
case 'expression':
$arguments[$key] = new Expression((string) $arg);
break;
case 'collection': case 'collection':
$arguments[$key] = $arg->getArgumentsAsPhp($name, false); $arguments[$key] = $arg->getArgumentsAsPhp($name, false);
break; break;

View File

@ -25,6 +25,7 @@ use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Scope; use Symfony\Component\DependencyInjection\Scope;
use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\ExpressionLanguage\Expression;
class ContainerBuilderTest extends \PHPUnit_Framework_TestCase class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
{ {
@ -377,6 +378,15 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
$builder->get('foo'); $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 * @covers Symfony\Component\DependencyInjection\ContainerBuilder::resolveServices
*/ */
@ -386,6 +396,7 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
$builder->register('foo', 'FooClass'); $builder->register('foo', 'FooClass');
$this->assertEquals($builder->get('foo'), $builder->resolveServices(new Reference('foo')), '->resolveServices() resolves service references to service instances'); $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(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');
} }
/** /**

View File

@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\ExpressionLanguage\Expression;
$container = new ContainerBuilder(); $container = new ContainerBuilder();
$container-> $container->
@ -50,7 +51,8 @@ $container->
addMethodCall('setBar', array(new Reference('foo')))-> addMethodCall('setBar', array(new Reference('foo')))->
addMethodCall('setBar', array(new Reference('foo2', ContainerInterface::NULL_ON_INVALID_REFERENCE)))-> 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('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-> $container->
register('factory_service', 'Bar')-> register('factory_service', 'Bar')->

View File

@ -8,6 +8,7 @@ function sc_configure($instance)
class BarClass class BarClass
{ {
protected $baz; protected $baz;
public $foo = 'foo';
public function setBaz(BazClass $baz) public function setBaz(BazClass $baz)
{ {

View File

@ -198,6 +198,7 @@ class ProjectServiceContainer extends Container
if ($this->has('foobaz')) { if ($this->has('foobaz')) {
$instance->setBar($this->get('foobaz', ContainerInterface::NULL_ON_INVALID_REFERENCE)); $instance->setBar($this->get('foobaz', ContainerInterface::NULL_ON_INVALID_REFERENCE));
} }
$instance->setBar(($this->get("foo")->foo() . $this->getParameter("foo")));
return $instance; return $instance;
} }

View File

@ -203,6 +203,7 @@ class ProjectServiceContainer extends Container
$instance->setBar($this->get('foo')); $instance->setBar($this->get('foo'));
$instance->setBar(NULL); $instance->setBar(NULL);
$instance->setBar(($this->get("foo")->foo() . $this->getParameter("foo")));
return $instance; return $instance;
} }

View File

@ -32,6 +32,9 @@
</service> </service>
<service id="method_call1" class="FooClass"> <service id="method_call1" class="FooClass">
<call method="setBar" /> <call method="setBar" />
<call method="setBar">
<argument type="expression">service("foo").foo() ~ parameter("foo")</argument>
</call>
</service> </service>
<service id="method_call2" class="FooClass"> <service id="method_call2" class="FooClass">
<call method="setBar"> <call method="setBar">

View File

@ -49,6 +49,9 @@
<call method="setBar"> <call method="setBar">
<argument type="service" id="foobaz" on-invalid="ignore"/> <argument type="service" id="foobaz" on-invalid="ignore"/>
</call> </call>
<call method="setBar">
<argument type="expression">service("foo").foo() ~ parameter("foo")</argument>
</call>
</service> </service>
<service id="factory_service" class="Bar" factory-method="getInstance" factory-service="foo.baz"/> <service id="factory_service" class="Bar" factory-method="getInstance" factory-service="foo.baz"/>
<service id="foo_with_inline" class="Foo"> <service id="foo_with_inline" class="Foo">

View File

@ -15,6 +15,7 @@ services:
calls: calls:
- [ setBar, [] ] - [ setBar, [] ]
- [ setBar ] - [ setBar ]
- [ setBar, ['@=service("foo").foo() ~ parameter("foo")'] ]
method_call2: method_call2:
class: FooClass class: FooClass
calls: calls:

View File

@ -38,6 +38,7 @@ services:
- [setBar, ['@?foo2']] - [setBar, ['@?foo2']]
- [setBar, ['@?foo3']] - [setBar, ['@?foo3']]
- [setBar, ['@?foobaz']] - [setBar, ['@?foobaz']]
- [setBar, ['@=service("foo").foo() ~ parameter("foo")']]
factory_service: factory_service:
class: Bar class: Bar

View File

@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Loader\IniFileLoader; use Symfony\Component\DependencyInjection\Loader\IniFileLoader;
use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\FileLocator;
use Symfony\Component\ExpressionLanguage\Expression;
class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase 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('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(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('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->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->assertNull($services['factory_service']->getClass());
$this->assertEquals('getInstance', $services['factory_service']->getFactoryMethod()); $this->assertEquals('getInstance', $services['factory_service']->getFactoryMethod());

View File

@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Loader\IniFileLoader; use Symfony\Component\DependencyInjection\Loader\IniFileLoader;
use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\FileLocator;
use Symfony\Component\ExpressionLanguage\Expression;
class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase 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('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(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('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(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()); $this->assertEquals('baz_factory', $services['factory_service']->getFactoryService());

View File

@ -20,7 +20,8 @@
}, },
"require-dev": { "require-dev": {
"symfony/yaml": "~2.0", "symfony/yaml": "~2.0",
"symfony/config": "~2.2" "symfony/config": "~2.2",
"symfony/expression-language": "~2.4"
}, },
"suggest": { "suggest": {
"symfony/yaml": "", "symfony/yaml": "",