Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 47ab06d

Browse files
[DI][EventDispatcher] Add & wire closure-proxy argument type
1 parent e98c068 commit 47ab06d

File tree

11 files changed

+206
-50
lines changed

11 files changed

+206
-50
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Reference;
15+
16+
/**
17+
* @author Nicolas Grekas <[email protected]>
18+
*/
19+
class ClosureProxyArgument
20+
{
21+
private $reference;
22+
private $method;
23+
24+
public function __construct(Reference $reference, $method)
25+
{
26+
$this->reference = $reference;
27+
$this->method = $method;
28+
}
29+
30+
/**
31+
* {@inheritdoc}
32+
*/
33+
public function getValues()
34+
{
35+
return array($this->reference, $this->method);
36+
}
37+
38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function setValues(array $values)
42+
{
43+
list($this->reference, $this->method) = $values;
44+
}
45+
}

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

Lines changed: 105 additions & 0 deletions
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\Variable;
1516
use Symfony\Component\DependencyInjection\Definition;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -1398,6 +1399,13 @@ private function dumpValue($value, $interpolate = true)
13981399
}
13991400

14001401
return sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments));
1402+
} elseif ($value instanceof ClosureProxyArgument) {
1403+
list($reference, $method) = $value->getValues();
1404+
1405+
$class = $this->container->findDefinition((string) $reference)->getClass();
1406+
$r = new \ReflectionMethod($class, $method);
1407+
1408+
return sprintf('/** @closure-proxy %s::%s */ function %s { return %s->%s; }', $class, $method, $this->generateSignature($r), $this->dumpValue($reference), $this->generateCall($r));
14011409
} elseif ($value instanceof Variable) {
14021410
return '$'.$value;
14031411
} elseif ($value instanceof Reference) {
@@ -1654,4 +1662,101 @@ private function doExport($value)
16541662

16551663
return $export;
16561664
}
1665+
1666+
private function generateSignature(\ReflectionFunctionAbstract $r)
1667+
{
1668+
$signature = array();
1669+
1670+
foreach ($r->getParameters() as $p) {
1671+
$k = '$'.$p->name;
1672+
if (method_exists($p, 'isVariadic') && $p->isVariadic()) {
1673+
$k = '...'.$k;
1674+
}
1675+
if ($p->isPassedByReference()) {
1676+
$k = '&'.$k;
1677+
}
1678+
if (method_exists($p, 'getType')) {
1679+
if ($type = $p->getType()) {
1680+
$k = $this->generateTypeHint($type, $r).' '.$k;
1681+
}
1682+
} elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $p, $type)) {
1683+
$k = $type[1].' '.$k;
1684+
}
1685+
if ($type && $p->allowsNull()) {
1686+
$k = '?'.$k;
1687+
}
1688+
1689+
try {
1690+
$k .= ' = '.$this->dumpValue($p->getDefaultValue(), false);
1691+
if ($type && $p->allowsNull() && null === $p->getDefaultValue()) {
1692+
$k = substr($k, 1);
1693+
}
1694+
} catch (\ReflectionException $e) {
1695+
if ($type && $p->allowsNull() && !class_exists('ReflectionNamedType', false)) {
1696+
$k .= ' = null';
1697+
$k = substr($k, 1);
1698+
}
1699+
}
1700+
1701+
$signature[] = $k;
1702+
}
1703+
1704+
$signature = ($r->returnsReference() ? '&(' : '(').implode(', ', $signature).')';
1705+
1706+
if (method_exists($r, 'getReturnType') && $type = $r->getReturnType()) {
1707+
$signature .= ': '.($type->allowsNull() ? '?' : '').$this->generateTypeHint($type, $r);
1708+
}
1709+
1710+
return $signature;
1711+
}
1712+
1713+
private function generateCall(\ReflectionFunctionAbstract $r)
1714+
{
1715+
$signature = array();
1716+
1717+
foreach ($r->getParameters() as $p) {
1718+
$k = '$'.$p->name;
1719+
if (method_exists($p, 'isVariadic') && $p->isVariadic()) {
1720+
$k = '...'.$k;
1721+
}
1722+
1723+
$signature[] = $k;
1724+
}
1725+
1726+
$signature = ($r->isClosure() ? '' : $r->name).'('.implode(', ', $signature).')';
1727+
1728+
return $signature;
1729+
}
1730+
1731+
private function generateTypeHint($type, \ReflectionFunctionAbstract $r)
1732+
{
1733+
$type = $type instanceof \ReflectionNamedType ? $type->getName() : $type->__toString();
1734+
1735+
switch (strtolower($type)) {
1736+
case 'parent':
1737+
if ($r instanceof \ReflectionMethod) {
1738+
$type = '\\'.$r->getDeclaringClass()->getParentClass()->name;
1739+
}
1740+
1741+
return $type;
1742+
1743+
case 'self':
1744+
if ($r instanceof \ReflectionMethod) {
1745+
$type = '\\'.$r->getDeclaringClass()->name;
1746+
}
1747+
1748+
return $type;
1749+
1750+
case 'array':
1751+
case 'bool':
1752+
case 'float':
1753+
case 'int':
1754+
case 'iterable':
1755+
case 'string':
1756+
case 'void':
1757+
return $type;
1758+
}
1759+
1760+
return '\\'.$type;
1761+
}
16571762
}

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

Lines changed: 5 additions & 0 deletions
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\Parameter;
1617
use Symfony\Component\DependencyInjection\Reference;
@@ -283,6 +284,10 @@ private function convertParameters(array $parameters, $type, \DOMElement $parent
283284
if (is_array($value)) {
284285
$element->setAttribute('type', 'collection');
285286
$this->convertParameters($value, $type, $element, 'key');
287+
} elseif ($value instanceof ClosureProxyArgument) {
288+
list($reference, $method) = $value->getValues();
289+
$element->setAttribute('type', 'closure-proxy');
290+
$element->setAttribute('id', $reference.':'.$method);
286291
} elseif ($value instanceof Reference) {
287292
$element->setAttribute('type', 'service');
288293
$element->setAttribute('id', (string) $value);

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Dumper;
1313

1414
use Symfony\Component\Yaml\Dumper as YmlDumper;
15+
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1516
use Symfony\Component\DependencyInjection\Alias;
1617
use Symfony\Component\DependencyInjection\ContainerInterface;
1718
use Symfony\Component\DependencyInjection\Definition;
@@ -252,6 +253,10 @@ private function dumpValue($value)
252253
}
253254

254255
return $code;
256+
} elseif ($value instanceof ClosureProxyArgument) {
257+
list($reference, $method) = $value->getValues();
258+
259+
return array($this->getServiceCall((string) $reference, $reference), $method);
255260
} elseif ($value instanceof Reference) {
256261
return $this->getServiceCall((string) $value, $value);
257262
} elseif ($value instanceof Parameter) {

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

Lines changed: 7 additions & 0 deletions
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\Reference;
@@ -398,6 +399,12 @@ private function getArgumentsAsPhp(\DOMElement $node, $name, $lowercase = true)
398399
case 'expression':
399400
$arguments[$key] = new Expression($arg->nodeValue);
400401
break;
402+
case 'closure-proxy':
403+
if (2 !== count($id = explode(':', $arg->getAttribute('id')))) {
404+
throw new InvalidArgumentException(sprintf('The "id" of closure-proxy arguments must be formatted as "%service%:%method%", got "%s".', $arg->getAttribute('id')));
405+
}
406+
$arguments[$key] = new ClosureProxyArgument(new Reference($id[0]), $id[1]);
407+
break;
401408
case 'collection':
402409
$arguments[$key] = $this->getArgumentsAsPhp($arg, $name, false);
403410
break;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@
189189
<xsd:enumeration value="expression" />
190190
<xsd:enumeration value="string" />
191191
<xsd:enumeration value="constant" />
192+
<xsd:enumeration value="closure-proxy" />
192193
</xsd:restriction>
193194
</xsd:simpleType>
194195

src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,13 @@ public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatc
4646
$this->name = is_object($listener[0]) ? get_class($listener[0]) : $listener[0];
4747
$this->pretty = $this->name.'::'.$listener[1];
4848
} elseif ($listener instanceof \Closure) {
49-
$this->pretty = $this->name = 'closure';
49+
$r = new \ReflectionFunction($listener);
50+
if (preg_match('#^/\*\* @closure-proxy ([^: ]++)::([^: ]++) \*/$#', $r->getDocComment(), $m)) {
51+
$this->name = $m[1];
52+
$this->pretty = $m[1].'::'.$m[2];
53+
} else {
54+
$this->pretty = $this->name = 'closure';
55+
}
5056
} elseif (is_string($listener)) {
5157
$this->pretty = $this->name = $listener;
5258
} else {

src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
namespace Symfony\Component\EventDispatcher\DependencyInjection;
1313

14+
use Symfony\Component\DependencyInjection\Argument\ClosureProxyArgument;
1415
use Symfony\Component\DependencyInjection\ContainerBuilder;
1516
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1617
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
18+
use Symfony\Component\DependencyInjection\Reference;
1719

1820
/**
1921
* Compiler pass to register tagged services for an event dispatcher.
@@ -59,10 +61,6 @@ public function process(ContainerBuilder $container)
5961

6062
foreach ($container->findTaggedServiceIds($this->listenerTag) as $id => $events) {
6163
$def = $container->getDefinition($id);
62-
if (!$def->isPublic()) {
63-
throw new InvalidArgumentException(sprintf('The service "%s" must be public as event listeners are lazy-loaded.', $id));
64-
}
65-
6664
if ($def->isAbstract()) {
6765
throw new InvalidArgumentException(sprintf('The service "%s" must not be abstract as event listeners are lazy-loaded.', $id));
6866
}
@@ -82,16 +80,12 @@ public function process(ContainerBuilder $container)
8280
$event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
8381
}
8482

85-
$definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority));
83+
$definition->addMethodCall('addListener', array($event['event'], new ClosureProxyArgument(new Reference($id), $event['method']), $priority));
8684
}
8785
}
8886

8987
foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) {
9088
$def = $container->getDefinition($id);
91-
if (!$def->isPublic()) {
92-
throw new InvalidArgumentException(sprintf('The service "%s" must be public as event subscribers are lazy-loaded.', $id));
93-
}
94-
9589
if ($def->isAbstract()) {
9690
throw new InvalidArgumentException(sprintf('The service "%s" must not be abstract as event subscribers are lazy-loaded.', $id));
9791
}
@@ -108,7 +102,17 @@ public function process(ContainerBuilder $container)
108102
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
109103
}
110104

111-
$definition->addMethodCall('addSubscriberService', array($id, $class));
105+
foreach ($class::getSubscribedEvents() as $eventName => $params) {
106+
if (is_string($params)) {
107+
$definition->addMethodCall('addListener', array($eventName, new ClosureProxyArgument(new Reference($id), $params)));
108+
} elseif (is_string($params[0])) {
109+
$definition->addMethodCall('addListener', array($eventName, new ClosureProxyArgument(new Reference($id), $params[0]), isset($params[1]) ? $params[1] : 0));
110+
} else {
111+
foreach ($params as $listener) {
112+
$definition->addMethodCall('addListener', array($eventName, new ClosureProxyArgument(new Reference($id), $listener[0]), isset($listener[1]) ? $listener[1] : 0));
113+
}
114+
}
115+
}
112116
}
113117
}
114118
}

0 commit comments

Comments
 (0)