Skip to content

Commit 374e1b0

Browse files
[VarExporter] Add support for exporting named closures
1 parent 5c5494f commit 374e1b0

File tree

8 files changed

+115
-2
lines changed

8 files changed

+115
-2
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.4
5+
---
6+
7+
* Add support for exporting named closures
8+
49
7.3
510
---
611

Internal/Exporter.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
7373
goto handle_value;
7474
}
7575

76+
if ($value instanceof \Closure && !($r = new \ReflectionFunction($value))->isAnonymous()) {
77+
$callable = [$r->getClosureThis() ?? $r->getClosureCalledClass()?->name, $r->name];
78+
$r = $callable[0] ? new \ReflectionMethod(...$callable) : null;
79+
$value = new NamedClosure(self::prepare($callable, $objectsPool, $refsPool, $objectsCount, $valueIsStatic), $r);
80+
81+
goto handle_value;
82+
}
83+
7684
$class = $value::class;
7785
$reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class);
7886
$properties = [];
@@ -217,6 +225,19 @@ public static function export($value, $indent = '')
217225
}
218226
$subIndent = $indent.' ';
219227

228+
if ($value instanceof NamedClosure) {
229+
if ($value->method?->isPublic() ?? true) {
230+
return match (true) {
231+
null === $value->callable[0] => '\\'.$value->callable[1],
232+
\is_string($value->callable[0]) => '\\'.$value->callable[0].'::'.$value->callable[1],
233+
\is_object($value->callable[0]) => self::export($value->callable[0], $subIndent).'->'.$value->callable[1],
234+
}.'(...)';
235+
}
236+
237+
return 'new \ReflectionMethod(\\'.$value->method->class.'::class, '.self::export($value->callable[1]).')'
238+
.'->getClosure('.(\is_object($value->callable[0]) ? self::export($value->callable[0]) : '').')';
239+
}
240+
220241
if (\is_string($value)) {
221242
$code = \sprintf("'%s'", addcslashes($value, "'\\"));
222243

Internal/NamedClosure.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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\VarExporter\Internal;
13+
14+
/**
15+
* @author Nicolas Grekas <p@tchwork.com>
16+
*
17+
* @internal
18+
*/
19+
class NamedClosure
20+
{
21+
public function __construct(
22+
public readonly array $callable,
23+
public readonly ?\ReflectionMethod $method = null,
24+
) {
25+
}
26+
}

Tests/Fixtures/PrivateFCC.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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\VarExporter\Tests\Fixtures;
13+
14+
#[\Attribute(\Attribute::TARGET_CLASS)]
15+
#[PrivateFCC(self::testMethod(...))]
16+
class PrivateFCC
17+
{
18+
private static function testMethod()
19+
{
20+
}
21+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
4+
$o = [
5+
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['Symfony\\Component\\VarExporter\\Tests\\TestClass'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\TestClass')),
6+
],
7+
null,
8+
[],
9+
$o[0]->testMethod(...),
10+
[]
11+
);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
return \Symfony\Component\VarExporter\Tests\TestClass::testStaticMethod(...);

Tests/Fixtures/private-fcc.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
return new \ReflectionMethod(\Symfony\Component\VarExporter\Tests\Fixtures\PrivateFCC::class, 'testMethod')->getClosure();

Tests/VarExporterTest.php

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\VarExporter\Tests\Fixtures\FooSerializable;
2323
use Symfony\Component\VarExporter\Tests\Fixtures\FooUnitEnum;
2424
use Symfony\Component\VarExporter\Tests\Fixtures\MySerializable;
25+
use Symfony\Component\VarExporter\Tests\Fixtures\PrivateFCC;
2526
use Symfony\Component\VarExporter\VarExporter;
2627

2728
class VarExporterTest extends TestCase
@@ -80,7 +81,7 @@ public static function provideFailingSerialization()
8081
public function testExport(string $testName, $value, bool $staticValueExpected = false)
8182
{
8283
$dumpedValue = $this->getDump($value);
83-
$isStaticValue = true;
84+
$isStaticValue = null;
8485
$marshalledValue = VarExporter::export($value, $isStaticValue);
8586

8687
$this->assertSame($staticValueExpected, $isStaticValue);
@@ -99,7 +100,7 @@ public function testExport(string $testName, $value, bool $staticValueExpected =
99100
}
100101
$marshalledValue = include $fixtureFile;
101102

102-
if (!$isStaticValue) {
103+
if (!$isStaticValue || 'named-closure-static' === $testName || 'private-fcc' === $testName) {
103104
if ($value instanceof MyWakeup) {
104105
$value->bis = null;
105106
}
@@ -234,11 +235,20 @@ public static function provideExport()
234235
yield ['unit-enum', [FooUnitEnum::Bar], true];
235236
yield ['readonly', new FooReadonly('k', 'v')];
236237

238+
yield ['named-closure-method', (new TestClass())->testMethod(...)];
239+
yield ['named-closure-static', TestClass::testStaticMethod(...), true];
240+
237241
if (\PHP_VERSION_ID < 80400) {
238242
return;
239243
}
240244

241245
yield ['backed-property', new BackedProperty('name')];
246+
247+
if (\PHP_VERSION_ID < 80500) {
248+
return;
249+
}
250+
251+
yield ['private-fcc', (new \ReflectionClass(PrivateFCC::class))->getAttributes(PrivateFCC::class)[0]->getArguments()[0], true];
242252
}
243253

244254
public function testUnicodeDirectionality()
@@ -299,6 +309,19 @@ private function __construct($prop)
299309
}
300310
}
301311

312+
class TestClass
313+
{
314+
public function testMethod()
315+
{
316+
return 'test';
317+
}
318+
319+
public static function testStaticMethod()
320+
{
321+
return 'test';
322+
}
323+
}
324+
302325
class MyPrivateValue
303326
{
304327
protected $prot;

0 commit comments

Comments
 (0)