8000 [DI] Fix using private services in expressions · symfony/symfony@f3da6cf · GitHub
[go: up one dir, main page]

Skip to content

Commit f3da6cf

Browse files
[DI] Fix using private services in expressions
1 parent 945596b commit f3da6cf

File tree

8 files changed

+148
-6
lines changed

8 files changed

+148
-6
lines changed

src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

1414
use Symfony\Component\DependencyInjection\Definition;
15+
use Symfony\Component\DependencyInjection\ExpressionLanguage;
1516
use Symfony\Component\DependencyInjection\Reference;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\ExpressionLanguage\Expression;
1719

1820
/**
1921
* Run this pass before passes that need to know more about the relation of
@@ -32,6 +34,7 @@ class AnalyzeServiceReferencesPass implements RepeatablePassInterface
3234
private $currentDefinition;
3335
private $repeatedPass;
3436
private $onlyConstructorArguments;
37+
private $expressionLanguage;
3538

3639
/**
3740
* @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls
@@ -97,6 +100,8 @@ private function processArguments(array $arguments)
97100
foreach ($arguments as $argument) {
98101
if (is_array($argument)) {
99102
$this->processArguments($argument);
103+
} elseif ($argument instanceof Expression) {
104+
$this->getExpressionLanguage()->compile((string) $argument, array('this' => 'container'));
100105
} elseif ($argument instanceof Reference) {
101106
$this->graph->connect(
102107
$this->currentId,
@@ -143,4 +148,27 @@ private function getDefinitionId($id)
143148

144149
return $id;
145150
}
151+
152+
private function getExpressionLanguage()
153+
{
154+
if (null === $this->expressionLanguage) {
155+
$providers = $this->container->getExpressionLanguageProviders();
156+
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) {
157+
if ('""' === substr_replace($arg, '', 1, -1)) {
158+
$id = stripcslashes(substr($arg, 1, -1));
159+
160+
$this->graph->connect(
161+
$this->currentId,
162+
$this->currentDefinition,
163+
67E6 $this->getDefinitionId($id),
164+
$this->getDefinition($id)
165+
);
166+
}
167+
168+
return sprintf('$this->get(%s)', $arg);
169+
});
170+
}
171+
172+
return $this->expressionLanguage;
173+
}
146174
}

src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1597,7 +1597,15 @@ private function getExpressionLanguage()
15971597
throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
15981598
}
15991599
$providers = $this->container->getExpressionLanguageProviders();
1600-
$this->expressionLanguage = new ExpressionLanguage(null, $providers);
1600+
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) {
1601+
$id = '""' === substr_replace($arg, '', 1, -1) ? stripcslashes(substr($arg, 1, -1)) : null;
1602+
1603+
if (null !== $id && ($this->container->hasAlias($id) || $this->container->hasDefinition($id))) {
1604+
return $this->getServiceCall($id);
1605+
}
1606+
1607+
return sprintf('$this->get(%s)', $arg);
1608+
});
16011609

16021610
if ($this->container->isTrackingResources()) {
16031611
foreach ($providers as $provider) {

src/Symfony/Component/DependencyInjection/ExpressionLanguage.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ class ExpressionLanguage extends BaseExpressionLanguage
2525
/**
2626
* {@inheritdoc}
2727
*/
28-
public function __construct($cache = null, array $providers = array())
28+
public function __construct($cache = null, array $providers = array(), callable $serviceCompiler = null)
2929
{
3030
// prepend the default provider to let users override it easily
31-
array_unshift($providers, new ExpressionLanguageProvider());
31+
array_unshift($providers, new ExpressionLanguageProvider($serviceCompiler));
3232

3333
parent::__construct($cache, $providers);
3434
}

src/Symfony/Component/DependencyInjection/ExpressionLanguageProvider.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,17 @@
2424
*/
2525
class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface
2626
{
27+
private $serviceCompiler;
28+
29+
public function __construct(callable $serviceCompiler = null)
30+
{
31+
$this->serviceCompiler = $serviceCompiler;
32+
}
33+
2734
public function getFunctions()
2835
{
2936
return array(
30-
new ExpressionFunction('service', function ($arg) {
37+
new ExpressionFunction('service', $this->serviceCompiler ?: function ($arg) {
3138
return sprintf('$this->get(%s)', $arg);
3239
}, function (array $variables, $value) {
3340
return $variables['container']->get($value);

src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,22 @@ public function testPrivateWithIgnoreOnInvalidReference()
425425
$this->assertInstanceOf('BazClass', $container->get('bar')->getBaz());
426426
}
427427

428+
public function testExpressionReferencingPrivateService()
429+
{
430+
$container = new ContainerBuilder();
431+
$container->register('private_bar', 'stdClass')
432+
->setPublic(false);
433+
$container->register('private_foo', 'stdClass')
434+
->setPublic(false);
435+
$container->register('public_foo', 'stdClass')
436+
->addArgument(new Expression('service("private_foo")'));
437+
438+
$container->compile();
439+
$dumper = new PhpDumper($container);
440+
441+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_private_in_expression.php', $dumper->dump());
442+
}
443+
428444
public function testDumpHandlesLiteralClassWithRootNamespace()
429445
{
430446
$container = new ContainerBuilder();

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ protected function getMethodCall1Service()
306306
if ($this->has('foobaz')) {
307307
$instance->setBar($this->get('foobaz', ContainerInterface::NULL_ON_INVALID_REFERENCE));
308308
}
309-
$instance->setBar(($this->get("foo")->foo() . (($this->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default"))));
309+
$instance->setBar(($this->get('foo')->foo() . (($this->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default"))));
310310

311311
return $instance;
312312
}

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ protected function getMethodCall1Service()
299299

300300
$instance->setBar($this->get('foo'));
301301
$instance->setBar(NULL);
302-
$instance->setBar(($this->get("foo")->foo() . (($this->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default"))));
302+
$instance->setBar(($this->get('foo')->foo() . (($this->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default"))));
303303

304304
return $instance;
305305
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
use Symfony\Component\DependencyInjection\ContainerInterface;
4+
use Symfony\Component\DependencyInjection\Container;
5+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
6+
use Symfony\Component\DependencyInjection\Exception\LogicException;
7+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
8+
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
9+
10+
/**
11+
* ProjectServiceContainer.
12+
*
13+
* This class has been auto-generated
14+
* by the Symfony Dependency Injection Component.
15+
*/
16+
class ProjectServiceContainer extends Container
17+
{
18+
private $parameters;
19+
private $targetDirs = array();
20+
21+
/**
22+
* Constructor.
23+
*/
24+
public function __construct()
25+
{
26+
$this->services = array();
27+
$this->methodMap = array(
28+
'private_foo' => 'getPrivateFooService',
29+
'public_foo' => 'getPublicFooService',
30+
);
31+
$this->privates = array(
32+
'private_foo' => true,
33+
);
34+
35+
$this->aliases = array();
36+
}
37+
38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function compile()
42+
{
43+
throw new LogicException('You cannot compile a dumped frozen container.');
44+
}
45+
46+
/**
47+
* {@inheritdoc}
48+
*/
49+
public function isFrozen()
50+
{
51+
return true;
52+
}
53+
54+
/**
55+
* Gets the 'public_foo' service.
56+
*
57+
* This service is shared.
58+
* This method always returns the same instance of the service.
59+
*
60+
* @return \stdClass A stdClass instance
61+
*/
62+
protected function getPublicFooService()
63+
{
64+
return $this->services['public_foo'] = new \stdClass(${($_ = isset($this->services['private_foo']) ? $this->services['private_foo'] : $this->getPrivateFooService()) && false ?: '_'});
65+
}
66+
67+
/**
68+
* Gets the 'private_foo' service.
69+
*
70+
* This service is shared.
71+
* This method always returns the same instance of the service.
72+
*
73+
* This service is private.
74+
* If you want to be able to request this service from the container directly,
75+
* make it public, otherwise you might end up with broken code.
76+
*
77+
* @return \stdClass A stdClass instance
78+
*/
79+
protected function getPrivateFooService()
80+
{
81+
return $this->services['private_foo'] = new \stdClass();
82+
}
83+
}

0 commit comments

Comments
 (0)
0