[DI] Fix using private services in expressions
This commit is contained in:
parent
945596b7cf
commit
f3da6cf6a2
@ -12,8 +12,10 @@
|
|||||||
namespace Symfony\Component\DependencyInjection\Compiler;
|
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||||
|
|
||||||
use Symfony\Component\DependencyInjection\Definition;
|
use Symfony\Component\DependencyInjection\Definition;
|
||||||
|
use Symfony\Component\DependencyInjection\ExpressionLanguage;
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\ExpressionLanguage\Expression;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run this pass before passes that need to know more about the relation of
|
* Run this pass before passes that need to know more about the relation of
|
||||||
@ -32,6 +34,7 @@ class AnalyzeServiceReferencesPass implements RepeatablePassInterface
|
|||||||
private $currentDefinition;
|
private $currentDefinition;
|
||||||
private $repeatedPass;
|
private $repeatedPass;
|
||||||
private $onlyConstructorArguments;
|
private $onlyConstructorArguments;
|
||||||
|
private $expressionLanguage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls
|
* @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls
|
||||||
@ -97,6 +100,8 @@ class AnalyzeServiceReferencesPass implements RepeatablePassInterface
|
|||||||
foreach ($arguments as $argument) {
|
foreach ($arguments as $argument) {
|
||||||
if (is_array($argument)) {
|
if (is_array($argument)) {
|
||||||
$this->processArguments($argument);
|
$this->processArguments($argument);
|
||||||
|
} elseif ($argument instanceof Expression) {
|
||||||
|
$this->getExpressionLanguage()->compile((string) $argument, array('this' => 'container'));
|
||||||
} elseif ($argument instanceof Reference) {
|
} elseif ($argument instanceof Reference) {
|
||||||
$this->graph->connect(
|
$this->graph->connect(
|
||||||
$this->currentId,
|
$this->currentId,
|
||||||
@ -143,4 +148,27 @@ class AnalyzeServiceReferencesPass implements RepeatablePassInterface
|
|||||||
|
|
||||||
return $id;
|
return $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getExpressionLanguage()
|
||||||
|
{
|
||||||
|
if (null === $this->expressionLanguage) {
|
||||||
|
$providers = $this->container->getExpressionLanguageProviders();
|
||||||
|
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) {
|
||||||
|
if ('""' === substr_replace($arg, '', 1, -1)) {
|
||||||
|
$id = stripcslashes(substr($arg, 1, -1));
|
||||||
|
|
||||||
|
$this->graph->connect(
|
||||||
|
$this->currentId,
|
||||||
|
$this->currentDefinition,
|
||||||
|
$this->getDefinitionId($id),
|
||||||
|
$this->getDefinition($id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf('$this->get(%s)', $arg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->expressionLanguage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1597,7 +1597,15 @@ EOF;
|
|||||||
throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
|
throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
|
||||||
}
|
}
|
||||||
$providers = $this->container->getExpressionLanguageProviders();
|
$providers = $this->container->getExpressionLanguageProviders();
|
||||||
$this->expressionLanguage = new ExpressionLanguage(null, $providers);
|
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) {
|
||||||
|
$id = '""' === substr_replace($arg, '', 1, -1) ? stripcslashes(substr($arg, 1, -1)) : null;
|
||||||
|
|
||||||
|
if (null !== $id && ($this->container->hasAlias($id) || $this->container->hasDefinition($id))) {
|
||||||
|
return $this->getServiceCall($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf('$this->get(%s)', $arg);
|
||||||
|
});
|
||||||
|
|
||||||
if ($this->container->isTrackingResources()) {
|
if ($this->container->isTrackingResources()) {
|
||||||
foreach ($providers as $provider) {
|
foreach ($providers as $provider) {
|
||||||
|
@ -25,10 +25,10 @@ class ExpressionLanguage extends BaseExpressionLanguage
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function __construct($cache = null, array $providers = array())
|
public function __construct($cache = null, array $providers = array(), callable $serviceCompiler = null)
|
||||||
{
|
{
|
||||||
// prepend the default provider to let users override it easily
|
// prepend the default provider to let users override it easily
|
||||||
array_unshift($providers, new ExpressionLanguageProvider());
|
array_unshift($providers, new ExpressionLanguageProvider($serviceCompiler));
|
||||||
|
|
||||||
parent::__construct($cache, $providers);
|
parent::__construct($cache, $providers);
|
||||||
}
|
}
|
||||||
|
@ -24,10 +24,17 @@ use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
|
|||||||
*/
|
*/
|
||||||
class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface
|
class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface
|
||||||
{
|
{
|
||||||
|
private $serviceCompiler;
|
||||||
|
|
||||||
|
public function __construct(callable $serviceCompiler = null)
|
||||||
|
{
|
||||||
|
$this->serviceCompiler = $serviceCompiler;
|
||||||
|
}
|
||||||
|
|
||||||
public function getFunctions()
|
public function getFunctions()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
new ExpressionFunction('service', function ($arg) {
|
new ExpressionFunction('service', $this->serviceCompiler ?: function ($arg) {
|
||||||
return sprintf('$this->get(%s)', $arg);
|
return sprintf('$this->get(%s)', $arg);
|
||||||
}, function (array $variables, $value) {
|
}, function (array $variables, $value) {
|
||||||
return $variables['container']->get($value);
|
return $variables['container']->get($value);
|
||||||
|
@ -425,6 +425,22 @@ class PhpDumperTest extends TestCase
|
|||||||
$this->assertInstanceOf('BazClass', $container->get('bar')->getBaz());
|
$this->assertInstanceOf('BazClass', $container->get('bar')->getBaz());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testExpressionReferencingPrivateService()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register('private_bar', 'stdClass')
|
||||||
|
->setPublic(false);
|
||||||
|
$container->register('private_foo', 'stdClass')
|
||||||
|
->setPublic(false);
|
||||||
|
$container->register('public_foo', 'stdClass')
|
||||||
|
->addArgument(new Expression('service("private_foo")'));
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
$dumper = new PhpDumper($container);
|
||||||
|
|
||||||
|
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_private_in_expression.php', $dumper->dump());
|
||||||
|
}
|
||||||
|
|
||||||
public function testDumpHandlesLiteralClassWithRootNamespace()
|
public function testDumpHandlesLiteralClassWithRootNamespace()
|
||||||
{
|
{
|
||||||
$container = new ContainerBuilder();
|
$container = new ContainerBuilder();
|
||||||
|
@ -306,7 +306,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->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default"))));
|
$instance->setBar(($this->get('foo')->foo() . (($this->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default"))));
|
||||||
|
|
||||||
return $instance;
|
return $instance;
|
||||||
}
|
}
|
||||||
|
@ -299,7 +299,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->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default"))));
|
$instance->setBar(($this->get('foo')->foo() . (($this->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default"))));
|
||||||
|
|
||||||
return $instance;
|
return $instance;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\Container;
|
||||||
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
||||||
|
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||||
|
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ProjectServiceContainer.
|
||||||
|
*
|
||||||
|
* This class has been auto-generated
|
||||||
|
* by the Symfony Dependency Injection Component.
|
||||||
|
*/
|
||||||
|
class ProjectServiceContainer extends Container
|
||||||
|
{
|
||||||
|
private $parameters;
|
||||||
|
private $targetDirs = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->services = array();
|
||||||
|
$this->methodMap = array(
|
||||||
|
'private_foo' => 'getPrivateFooService',
|
||||||
|
'public_foo' => 'getPublicFooService',
|
||||||
|
);
|
||||||
|
$this->privates = array(
|
||||||
|
'private_foo' => true,
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->aliases = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function compile()
|
||||||
|
{
|
||||||
|
throw new LogicException('You cannot compile a dumped frozen container.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isFrozen()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the 'public_foo' service.
|
||||||
|
*
|
||||||
|
* This service is shared.
|
||||||
|
* This method always returns the same instance of the service.
|
||||||
|
*
|
||||||
|
* @return \stdClass A stdClass instance
|
||||||
|
*/
|
||||||
|
protected function getPublicFooService()
|
||||||
|
{
|
||||||
|
return $this->services['public_foo'] = new \stdClass(${($_ = isset($this->services['private_foo']) ? $this->services['private_foo'] : $this->getPrivateFooService()) && false ?: '_'});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the 'private_foo' 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 \stdClass A stdClass instance
|
||||||
|
*/
|
||||||
|
protected function getPrivateFooService()
|
||||||
|
{
|
||||||
|
return $this->services['private_foo'] = new \stdClass();
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user