Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit aebd082

Browse filesBrowse files
[DI] add LazyString for lazy computation of string values injected into services
1 parent cbc4efc commit aebd082
Copy full SHA for aebd082

File tree

Expand file treeCollapse file tree

5 files changed

+195
-6
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+195
-6
lines changed

‎src/Symfony/Component/DependencyInjection/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ CHANGELOG
1313
* made singly-implemented interfaces detection be scoped by file
1414
* added ability to define a static priority method for tagged service
1515
* added support for improved syntax to define method calls in Yaml
16+
* added `LazyString` for lazy computation of string values injected into services
1617

1718
4.3.0
1819
-----
+112Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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;
13+
14+
/**
15+
* A string whose value is computed lazily by a callback.
16+
*
17+
* @author Nicolas Grekas <p@tchwork.com>
18+
*/
19+
class LazyString
20+
{
21+
private $value;
22+
23+
/**
24+
* @param callable $callback A callable or a [Closure, method] lazy-callable
25+
*
26+
* @return static
27+
*/
28+
public static function fromCallable($callback, ...$arguments): self
29+
{
30+
if (!\is_callable($callback) && !(\is_array($callback) && isset($callback[0]) && $callback[0] instanceof \Closure && 2 >= \count($callback))) {
31+
throw new \TypeError(sprintf('Argument 1 passed to %s() must be a callable or a [Closure, method] lazy-callable, %s given.', __METHOD__, \gettype($callback)));
32+
}
33+
34+
$lazyString = new static();
35+
$lazyString->value = static function () use (&$callback, &$arguments, &$value): string {
36+
if (null !== $arguments) {
37+
if (!\is_callable($callback)) {
38+
$callback[0] = $callback[0]();
39+
$callback[1] = $callback[1] ?? '__invoke';
40+
}
41+
$value = $callback(...$arguments);
42+
$callback = self::getPrettyName($callback);
43+
$arguments = null;
44+
}
45+
46+
return $value ?? '';
47+
};
48+
49+
return $lazyString;
50+
}
51+
52+
public function __toString()
53+
{
54+
if (\is_string($this->value)) {
55+
return $this->value;
56+
}
57+
58+
try {
59+
return $this->value = ($this->value)();
60+
} catch (\Throwable $e) {
61+
if (\TypeError::class === \get_class($e) && __FILE__ === $e->getFile()) {
62+
$type = explode(', ', $e->getMessage());
63+
$type = substr(array_pop($type), 0, -\strlen(' returned'));
64+
$r = new \ReflectionFunction($this->value);
65+
$callback = $r->getStaticVariables()['callback'];
66+
67+
$e = new \TypeError(sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type));
68+
}
69+
70+
if (\PHP_VERSION_ID < 70400) {
71+
// leverage the ErrorHandler component with graceful fallback when it's not available
72+
return trigger_error($e, E_USER_ERROR);
73+
}
74+
75+
throw $e;
76+
}
77+
}
78+
79+
private function __construct()
80+
{
81+
}
82+
83+
private static function getPrettyName(callable $callback): string
84+
{
85+
if (\is_string($callback)) {
86+
return $callback;
87+
}
88+
89+
if (\is_array($callback)) {
90+
$class = \is_object($callback[0]) ? \get_class($callback[0]) : $callback[0];
91+
$method = $callback[1];
92+
} elseif ($callback instanceof \Closure) {
93+
$r = new \ReflectionFunction($callback);
94+
95+
if (false !== strpos($r->name, '{closure}') || !$class = $r->getClosureScopeClass()) {
96+
return $r->name;
97+
}
98+
99+
$class = $class->name;
100+
$method = $r->name;
101+
} else {
102+
$class = \get_class($callback);
103+
$method = '__invoke';
104+
}
105+
106+
if (isset($class[15]) && "\0" === $class[15] && 0 === strpos($class, "class@anonymous\x00")) {
107+
$class = get_parent_class($class).'@anonymous';
108+
}
109+
110+
return $class.'::'.$method;
111+
}
112+
}
+70Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\LazyString;
16+
use Symfony\Component\ErrorHandler\ErrorHandler;
17+
18+
class LazyStringTest extends TestCase
19+
{
20+
public function testLazyString()
21+
{
22+
$count = 0;
23+
$s = LazyString::fromCallable(function () use (&$count) {
24+
return ++$count;
25+
});
26+
27+
$this->assertSame(0, $count);
28+
$this->assertSame('1', (string) $s);
29+
$this->assertSame(1, $count);
30+
}
31+
32+
public function testLazyCallable()
33+
{
34+
$count = 0;
35+
$s = LazyString::fromCallable([function () use (&$count) {
36+
return new class($count) {
37+
private $count;
38+
39+
public function __construct(int &$count)
40+
{
41+
$this->count = &$count;
42+
}
43+
44+
public function __invoke()
45+
{
46+
return ++$this->count;
47+
}
48+
};
49+
}]);
50+
51+
$this->assertSame(0, $count);
52+
$this->assertSame('1', (string) $s);
53+
$this->assertSame(1, $count);
54+
}
55+
56+
/**
57+
* @runInSeparateProcess
58+
*/
59+
public function testReturnTypeError()
60+
{
61+
ErrorHandler::register();
62+
63+
$s = LazyString::fromCallable(function () { return []; });
64+
65+
$this->expectException(\TypeError::class);
66+
$this->expectExceptionMessage('Return value of '.__NAMESPACE__.'\{closure}() passed to '.LazyString::class.'::fromCallable() must be of the type string, array returned.');
67+
68+
(string) $s;
69+
}
70+
}

‎src/Symfony/Component/DependencyInjection/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/composer.json
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"require-dev": {
2424
"symfony/yaml": "^3.4|^4.0|^5.0",
2525
"symfony/config": "^4.3|^5.0",
26+
"symfony/error-handler": "^4.4|^5.0",
2627
"symfony/expression-language": "^3.4|^4.0|^5.0"
2728
},
2829
"suggest": {

‎src/Symfony/Component/EventDispatcher/EventDispatcher.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/EventDispatcher/EventDispatcher.php
+11-6Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,16 @@ public function getListenerPriority($eventName, $listener)
111111
return null;
112112
}
113113

114-
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
114+
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
115115
$listener[0] = $listener[0]();
116+
$listener[1] = $listener[1] ?? '__invoke';
116117
}
117118

118119
foreach ($this->listeners[$eventName] as $priority => &$listeners) {
119120
foreach ($listeners as &$v) {
120-
if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) {
121+
if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
121122
$v[0] = $v[0]();
123+
$v[1] = $v[1] ?? '__invoke';
122124
}
123125
if ($v === $listener) {
124126
return $priority;
@@ -165,13 +167,14 @@ public function removeListener($eventName, $listener)
165167
return;
166168
}
167169

168-
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
170+
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
169171
$listener[0] = $listener[0]();
172+
$listener[1] = $listener[1] ?? '__invoke';
170173
}
171174

172175
foreach ($this->listeners[$eventName] as $priority => &$listeners) {
173176
foreach ($listeners as $k => &$v) {
174-
if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) {
177+
if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
175178
$v[0] = $v[0]();
176179
}
177180
if ($v === $listener) {
@@ -271,8 +274,9 @@ private function sortListeners(string $eventName)
271274

272275
foreach ($this->listeners[$eventName] as &$listeners) {
273276
foreach ($listeners as $k => $listener) {
274-
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
277+
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
275278
$listener[0] = $listener[0]();
279+
$listener[1] = $listener[1] ?? '__invoke';
276280
}
277281
$this->sorted[$eventName][] = $listener;
278282
}
@@ -290,10 +294,11 @@ private function optimizeListeners(string $eventName): array
290294
foreach ($this->listeners[$eventName] as &$listeners) {
291295
foreach ($listeners as &$listener) {
292296
$closure = &$this->optimized[$eventName][];
293-
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) {
297+
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
294298
$closure = static function (...$args) use (&$listener, &$closure) {
295299
if ($listener[0] instanceof \Closure) {
296300
$listener[0] = $listener[0]();
301+
$listener[1] = $listener[1] ?? '__invoke';
297302
}
298303
($closure = \Closure::fromCallable($listener))(...$args);
299304
};

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.