Skip to content

Commit 2cb1ba9

Browse files
[DI][EventDispatcher] Add & wire closure-proxy argument type
1 parent 51e2d70 commit 2cb1ba9

File tree

18 files changed

+599
-58
lines changed

18 files changed

+599
-58
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Argument;
13+
14+
use Symfony\Component\DependencyInjection\ContainerInterface;
15+
use Symfony\Component\DependencyInjection\Reference;
16+
17+
/**
18+
* @author Nicolas Grekas <p@tchwork.com>
19+
*/
20+
class ClosureProxyArgument implements ArgumentInterface
21+
{
22+
private $reference;
23+
private $method;
24+
25+
public function __construct($id, $method, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
26+
{
27+
$this->reference = new Reference($id, $invalidBehavior);
28+
$this->method = $method;
29+
}
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function getValues()
35+
{
36+
return array($this->reference, $this->method);
37+
}
38+
39+
/**
40+
* {@inheritdoc}
41+
*/
42+
public function setValues(array $values)
43+
{
44+
list($this->reference, $this->method) = $values;
45+
}
46+
}

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

+22
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection;
1313

14+
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1415
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1516
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
1617
use Symfony\Component\DependencyInjection\Compiler\Compiler;
@@ -976,6 +977,27 @@ public function resolveServices($value)
976977
yield $k => $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($v)));
977978
}
978979
});
980+
} elseif ($value instanceof ClosureProxyArgument) {
981+
list($reference, $method) = $value->getValues();
982+
if ('service_container' === $id = (string) $reference) {
983+
$class = parent::class;
984+
} elseif (!$this->hasDefinition($id) && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) {
985+
return null;
986+
} else {
987+
$class = $this->findDefinition($id)->getClass();
988+
}
989+
$r = new \ReflectionMethod($class, $method);
990+
if (!$r->isPublic()) {
991+
throw new InvalidArgumentException(sprintf('Service "%s": cannot create closure-proxy for non-public method "%s::%s".', $id, $class, $method));
992+
}
993+
foreach ($r->getParameters() as $p) {
994+
if ($p->isPassedByReference()) {
995+
throw new RuntimeException(sprintf('Service "%s": cannot create closure-proxy at runtime for method "%s::%s" because parameter "$%s" is passed by reference.', $id, $class, $method, $p->name));
996+
}
997+
}
998+
$value = function () use ($id, $method) {
999+
return call_user_func_array(array($this->get($id), $method), func_get_args());
1000+
};
9791001
} elseif ($value instanceof Reference) {
9801002
$value = $this->get((string) $value, $value->getInvalidBehavior());
9811003
} elseif ($value instanceof Definition) {

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

+120
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Dumper;
1313

14+
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1415
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1516
use Symfony\Component\DependencyInjection\Variable;
1617
use Symfony\Component\DependencyInjection\Definition;
@@ -62,6 +63,8 @@ class PhpDumper extends Dumper
6263
private $docStar;
6364
private $serviceIdToMethodNameMap;
6465
private $usedMethodNames;
66+
private $classResources = array();
67+
private $baseClass;
6568

6669
/**
6770
* @var \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface
@@ -117,7 +120,9 @@ public function dump(array $options = array())
117120
'debug' => true,
118121
), $options);
119122

123+
$this->classResources = array();
120124
$this->initializeMethodNamesMap($options['base_class']);
125+
$this->baseClass = $options['base_class'];
121126

122127
$this->docStar = $options['debug'] ? '*' : '';
123128

@@ -164,6 +169,11 @@ public function dump(array $options = array())
164169
;
165170
$this->targetDirRegex = null;
166171

172+
foreach ($this->classResources as $r) {
173+
$this->container->addClassResource($r);
174+
}
175+
$this->classResources = array();
176+
167177
$unusedEnvs = array();
168178
foreach ($this->container->getEnvCounters() as $env => $use) {
169179
if (!$use) {
@@ -1418,6 +1428,25 @@ private function dumpValue($value, $interpolate = true)
14181428
}
14191429

14201430
return sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments));
1431+
} elseif ($value instanceof ClosureProxyArgument) {
1432+
list($reference, $method) = $value->getValues();
1433+
1434+
if ('service_container' === (string) $reference) {
1435+
$class = $this->baseClass;
1436+
} elseif (!$this->container->hasDefinition((string) $reference) && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) {
1437+
return 'null';
1438+
} else {
1439+
$class = $this->container->findDefinition((string) $reference)->getClass();
1440+
}
1441+
if (!isset($this->classResources[$class])) {
1442+
$this->classResources[$class] = new \ReflectionClass($class);
1443+
}
1444+
$r = $this->classResources[$class]->getMethod($method);
1445+
if (!$r->isPublic()) {
1446+
throw new InvalidArgumentException(sprintf('Cannot create closure-proxy for non-public method "%s" of the "%s".', $method, $reference));
1447+
}
1448+
1449+
return sprintf("/** @closure-proxy %s::%s */ function %s {\n return %s->%s;\n }", $class, $method, $this->generateSignature($r), $this->dumpValue($reference), $this->generateCall($r));
14211450
} elseif ($value instanceof Variable) {
14221451
return '$'.$value;
14231452
} elseif ($value instanceof Reference) {
@@ -1674,4 +1703,95 @@ private function doExport($value)
16741703

16751704
return $export;
16761705
}
1706+
1707+
private function generateSignature(\ReflectionFunctionAbstract $r)
1708+
{
1709+
$signature = array();
1710+
1711+
foreach ($r->getParameters() as $p) {
1712+
$k = '$'.$p->name;
1713+
if (method_exists($p, 'isVariadic') && $p->isVariadic()) {
1714+
$k = '...'.$k;
1715+
}
1716+
if ($p->isPassedByReference()) {
1717+
$k = '&'.$k;
1718+
}
1719+
if (method_exists($p, 'getType')) {
1720+
$type = $p->getType();
1721+
} elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $p, $type)) {
1722+
$type = $type[1];
1723+
}
1724+
if ($type && $type = $this->generateTypeHint($type, $r)) {
1725+
$k = $type.' '.$k;
1726+
}
1727+
if ($type && $p->allowsNull()) {
1728+
$k = '?'.$k;
1729+
}
1730+
1731+
try {
1732+
$k .= ' = '.$this->dumpValue($p->getDefaultValue(), false);
1733+
if ($type && $p->allowsNull() && null === $p->getDefaultValue()) {
1734+
$k = substr($k, 1);
1735+
}
1736+
} catch (\ReflectionException $e) {
1737+
if ($type && $p->allowsNull() && !class_exists('ReflectionNamedType', false)) {
1738+
$k .= ' = null';
1739+
$k = substr($k, 1);
1740+
}
1741+
}
1742+
1743+
$signature[] = $k;
1744+
}
1745+
1746+
return ($r->returnsReference() ? '&(' : '(').implode(', ', $signature).')';
1747+
}
1748+
1749+
private function generateCall(\ReflectionFunctionAbstract $r)
1750+
{
1751+
$call = array();
1752+
1753+
foreach ($r->getParameters() as $p) {
1754+
$k = '$'.$p->name;
1755+
if (method_exists($p, 'isVariadic') && $p->isVariadic()) {
1756+
$k = '...'.$k;
1757+
}
1758+
1759+
$call[] = $k;
1760+
}
1761+
1762+
return ($r->isClosure() ? '' : $r->name).'('.implode(', ', $call).')';
1763+
}
1764+
1765+
private function generateTypeHint($type, \ReflectionFunctionAbstract $r)
1766+
{
1767+
if (is_string($type)) {
1768+
$name = $type;
1769+
1770+
if ('callable' === $name || 'array' === $name) {
1771+
return $name;
1772+
}
1773+
} else {
1774+
$name = $type instanceof \ReflectionNamedType ? $type->getName() : $type->__toString();
1775+
1776+
if ($type->isBuiltin()) {
1777+
return $name;
1778+
}
1779+
}
1780+
$lcName = strtolower($name);
1781+
1782+
if ('self' !== $lcName && 'parent' !== $lcName) {
1783+
return '\\'.$name;
1784+
}
1785+
if (!$r instanceof \ReflectionMethod) {
1786+
return null;
1787+
}
1788+
if ('self' === $lcName) {
1789+
return '\\'.$r->getDeclaringClass()->name;
1790+
}
1791+
if (!$parent = $r->getDeclaringClass()->getParentClass()) {
1792+
return null;
1793+
}
1794+
1795+
return '\\'.$parent->name;
1796+
}
16771797
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Dumper;
1313

14+
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1415
use Symfony\Component\DependencyInjection\ContainerInterface;
1516
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1617
use Symfony\Component\DependencyInjection\Parameter;
@@ -287,6 +288,11 @@ private function convertParameters(array $parameters, $type, \DOMElement $parent
287288
} elseif ($value instanceof IteratorArgument) {
288289
$element->setAttribute('type', 'iterator');
289290
$this->convertParameters($value->getValues(), $type, $element, 'key');
291+
} elseif ($value instanceof ClosureProxyArgument) {
292+
list($reference, $method) = $value->getValues();
293+
$element->setAttribute('type', 'closure-proxy');
294+
$element->setAttribute('id', (string) $reference);
295+
$element->setAttribute('method', $method);
290296
} elseif ($value instanceof Reference) {
291297
$element->setAttribute('type', 'service');
292298
$element->setAttribute('id', (string) $value);

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

+5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1515
use Symfony\Component\Yaml\Dumper as YmlDumper;
16+
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1617
use Symfony\Component\DependencyInjection\Alias;
1718
use Symfony\Component\DependencyInjection\ContainerInterface;
1819
use Symfony\Component\DependencyInjection\Definition;
@@ -257,6 +258,10 @@ private function dumpValue($value)
257258
}
258259

259260
return $code;
261+
} elseif ($value instanceof ClosureProxyArgument) {
262+
list($reference, $method) = $value->getValues();
263+
264+
return array($this->getServiceCall((string) $reference, $reference), $method);
260265
} elseif ($value instanceof Reference) {
261266
return $this->getServiceCall((string) $value, $value);
262267
} elseif ($value instanceof Parameter) {

src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

+12-8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Config\Util\XmlUtils;
1616
use Symfony\Component\DependencyInjection\ContainerInterface;
1717
use Symfony\Component\DependencyInjection\Alias;
18+
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1819
use Symfony\Component\DependencyInjection\Definition;
1920
use Symfony\Component\DependencyInjection\ChildDefinition;
2021
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
@@ -378,21 +379,24 @@ private function getArgumentsAsPhp(\DOMElement $node, $name, $lowercase = true)
378379
}
379380
}
380381

382+
$onInvalid = $arg->getAttribute('on-invalid');
383+
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
384+
if ('ignore' == $onInvalid) {
385+
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
386+
} elseif ('null' == $onInvalid) {
387+
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
388+
}
389+
381390
switch ($arg->getAttribute('type')) {
382391
case 'service':
383-
$onInvalid = $arg->getAttribute('on-invalid');
384-
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
385-
if ('ignore' == $onInvalid) {
386-
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
387-
} elseif ('null' == $onInvalid) {
388-
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
389-
}
390-
391392
$arguments[$key] = new Reference($arg->getAttribute('id'), $invalidBehavior);
392393
break;
393394
case 'expression':
394395
$arguments[$key] = new Expression($arg->nodeValue);
395396
break;
397+
case 'closure-proxy':
398+
$arguments[$key] = new ClosureProxyArgument($arg->getAttribute('id'), $arg->getAttribute('method'), $invalidBehavior);
399+
break;
396400
case 'collection':
397401
$arguments[$key] = $this->getArgumentsAsPhp($arg, $name, false);
398402
break;

src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd

+2
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@
164164
<xsd:attribute name="index" type="xsd:integer" />
165165
<xsd:attribute name="on-invalid" type="invalid_sequence" />
166166
<xsd:attribute name="strict" type="boolean" />
167+
<xsd:attribute name="method" type="xsd:string" />
167168
</xsd:complexType>
168169

169170
<xsd:complexType name="call" mixed="true">
@@ -190,6 +191,7 @@
190191
<xsd:enumeration value="string" />
191192
<xsd:enumeration value="constant" />
192193
<xsd:enumeration value="iterator" />
194+
<xsd:enumeration value="closure-proxy" />
193195
</xsd:restriction>
194196
</xsd:simpleType>
195197

0 commit comments

Comments
 (0)