Skip to content

Commit bc0539b

Browse files
TimWollaedoriantuqqu
committed
Support Closures in constant expressions
RFC: https://wiki.php.net/rfc/closures_in_const_expr Co-authored-by: Volker Dusch <[email protected]> Co-authored-by: Arthur Kurbidaev <[email protected]>
1 parent 17187c4 commit bc0539b

24 files changed

+574
-27
lines changed

NEWS

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ PHP NEWS
99
. Fixed bug GH-16665 (\array and \callable should not be usable in
1010
class_alias). (nielsdos)
1111
. Added PHP_BUILD_DATE constant. (cmb)
12+
. Added support for Closures in constant expressions. (timwolla,
13+
Volker Dusch)
1214

1315
- Curl:
1416
. Added curl_multi_get_handles(). (timwolla)

UPGRADING

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ PHP 8.5 UPGRADE NOTES
4444
2. New Features
4545
========================================
4646

47+
- Core:
48+
. Added support for Closures in constant expressions.
49+
RFC: https://wiki.php.net/rfc/closures_in_const_expr
50+
4751
- DOM:
4852
. Added Dom\Element::$outerHTML.
4953

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
--TEST--
2+
Allow defining closures in attributes
3+
--EXTENSIONS--
4+
reflection
5+
--FILE--
6+
<?php
7+
8+
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
9+
class Attr {
10+
public function __construct(public Closure $value) {
11+
$value('foo');
12+
}
13+
}
14+
15+
#[Attr(static function () { })]
16+
#[Attr(static function (...$args) {
17+
var_dump($args);
18+
})]
19+
class C {}
20+
21+
foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
22+
var_dump($reflectionAttribute->newInstance());
23+
}
24+
25+
?>
26+
--EXPECTF--
27+
object(Attr)#%d (1) {
28+
["value"]=>
29+
object(Closure)#%d (3) {
30+
["name"]=>
31+
string(%d) "{closure:%s:%d}"
32+
["file"]=>
33+
string(%d) "%s"
34+
["line"]=>
35+
int(%d)
36+
}
37+
}
38+
array(1) {
39+
[0]=>
40+
string(3) "foo"
41+
}
42+
object(Attr)#%d (1) {
43+
["value"]=>
44+
object(Closure)#%d (4) {
45+
["name"]=>
46+
string(%d) "{closure:%s:%d}"
47+
["file"]=>
48+
string(%d) "%s"
49+
["line"]=>
50+
int(%d)
51+
["parameter"]=>
52+
array(1) {
53+
["$args"]=>
54+
string(10) "<optional>"
55+
}
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
AST printing for closures in attributes
3+
--FILE--
4+
<?php
5+
6+
// Do not use `false &&` to fully evaluate the function / class definition.
7+
8+
try {
9+
\assert(
10+
!
11+
#[Attr(static function ($foo) {
12+
echo $foo;
13+
})]
14+
function () { }
15+
);
16+
} catch (Error $e) {
17+
echo $e->getMessage(), "\n";
18+
}
19+
20+
try {
21+
\assert(
22+
!
23+
new #[Attr(static function ($foo) {
24+
echo $foo;
25+
})]
26+
class {}
27+
);
28+
} catch (Error $e) {
29+
echo $e->getMessage(), "\n";
30+
}
31+
32+
?>
33+
--EXPECT--
34+
assert(!#[Attr(static function ($foo) {
35+
echo $foo;
36+
})] function () {
37+
})
38+
assert(!new #[Attr(static function ($foo) {
39+
echo $foo;
40+
})] class {
41+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
Closure in attribute may access private variables
3+
--EXTENSIONS--
4+
reflection
5+
--FILE--
6+
<?php
7+
8+
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
9+
class Attr {
10+
public function __construct(public Closure $value) {}
11+
}
12+
13+
#[Attr(static function (C $c) {
14+
echo $c->secret, PHP_EOL;
15+
})]
16+
class C {
17+
public function __construct(
18+
private string $secret,
19+
) {}
20+
}
21+
22+
foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
23+
($reflectionAttribute->newInstance()->value)(new C('secret'));
24+
}
25+
26+
?>
27+
--EXPECT--
28+
secret
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
Closure in attribute may not access unrelated private variables
3+
--EXTENSIONS--
4+
reflection
5+
--FILE--
6+
<?php
7+
8+
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
9+
class Attr {
10+
public function __construct(public Closure $value) {}
11+
}
12+
13+
#[Attr(static function (E $e) {
14+
echo $e->secret, PHP_EOL;
15+
})]
16+
class C {
17+
}
18+
19+
class E {
20+
public function __construct(
21+
private string $secret,
22+
) {}
23+
}
24+
25+
foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
26+
($reflectionAttribute->newInstance()->value)(new E('secret'));
27+
}
28+
29+
?>
30+
--EXPECTF--
31+
Fatal error: Uncaught Error: Cannot access private property E::$secret in %s:%d
32+
Stack trace:
33+
#0 %s(%d): C::{closure:%s:%d}(Object(E))
34+
#1 {main}
35+
thrown in %s on line %d
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Allow defining Closures in const expressions.
3+
--FILE--
4+
<?php
5+
6+
const Closure = static function () {
7+
echo "called", PHP_EOL;
8+
};
9+
10+
var_dump(Closure);
11+
(Closure)();
12+
13+
?>
14+
--EXPECTF--
15+
object(Closure)#%d (3) {
16+
["name"]=>
17+
string(%d) "{closure:%s:%d}"
18+
["file"]=>
19+
string(%d) "%s"
20+
["line"]=>
21+
int(3)
22+
}
23+
called
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Allow defining Closures in class constants.
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
const Closure = static function () {
8+
echo "called", PHP_EOL;
9+
};
10+
}
11+
12+
var_dump(C::Closure);
13+
(C::Closure)();
14+
15+
?>
16+
--EXPECTF--
17+
object(Closure)#%d (3) {
18+
["name"]=>
19+
string(%d) "{closure:%s:%d}"
20+
["file"]=>
21+
string(%d) "%s"
22+
["line"]=>
23+
int(4)
24+
}
25+
called
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
Allow defining Closures wrapped in an array in const expressions.
3+
--FILE--
4+
<?php
5+
6+
const Closure = [static function () {
7+
echo "called", PHP_EOL;
8+
}, static function () {
9+
echo "also called", PHP_EOL;
10+
}];
11+
12+
var_dump(Closure);
13+
14+
foreach (Closure as $closure) {
15+
$closure();
16+
}
17+
18+
?>
19+
--EXPECTF--
20+
array(2) {
21+
[0]=>
22+
object(Closure)#%d (3) {
23+
["name"]=>
24+
string(%d) "{closure:%s:%d}"
25+
["file"]=>
26+
string(%d) "%s"
27+
["line"]=>
28+
int(3)
29+
}
30+
[1]=>
31+
object(Closure)#%d (3) {
32+
["name"]=>
33+
string(%d) "{closure:%s:%d}"
34+
["file"]=>
35+
string(%d) "%s"
36+
["line"]=>
37+
int(5)
38+
}
39+
}
40+
called
41+
also called
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
Allow defining Closures passed as constructor arguments in const expressions.
3+
--FILE--
4+
<?php
5+
6+
class Dummy {
7+
public function __construct(
8+
public Closure $c,
9+
) {}
10+
}
11+
12+
const Closure = new Dummy(static function () {
13+
echo "called", PHP_EOL;
14+
});
15+
16+
var_dump(Closure);
17+
18+
(Closure->c)();
19+
20+
?>
21+
--EXPECTF--
22+
object(Dummy)#%d (1) {
23+
["c"]=>
24+
object(Closure)#%d (3) {
25+
["name"]=>
26+
string(%d) "{closure:%s:%d}"
27+
["file"]=>
28+
string(%d) "%s"
29+
["line"]=>
30+
int(9)
31+
}
32+
}
33+
called
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
Closures in default argument
3+
--FILE--
4+
<?php
5+
6+
function test(
7+
Closure $name = static function () {
8+
echo "default", PHP_EOL;
9+
},
10+
) {
11+
$name();
12+
}
13+
14+
test();
15+
test(function () {
16+
echo "explicit", PHP_EOL;
17+
});
18+
19+
?>
20+
--EXPECT--
21+
default
22+
explicit
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Disallows using non-static closures.
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public Closure $d = function () {
8+
var_dump($this);
9+
};
10+
}
11+
12+
$foo = new C();
13+
var_dump($foo->d);
14+
($foo->d)();
15+
16+
?>
17+
--EXPECTF--
18+
Fatal error: Closures in constant expressions must be static in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Disallows using variables.
3+
--FILE--
4+
<?php
5+
6+
$foo = "bar";
7+
8+
const Closure = static function () use ($foo) {
9+
echo $foo, PHP_EOL;
10+
};
11+
12+
var_dump(Closure);
13+
(Closure)();
14+
15+
?>
16+
--EXPECTF--
17+
Fatal error: Cannot use(...) variables in constant expression in %s on line %d
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Closure in property initializer
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public Closure $d = static function () {
8+
echo "called", PHP_EOL;
9+
};
10+
}
11+
12+
$c = new C();
13+
var_dump($c->d);
14+
($c->d)();
15+
16+
17+
?>
18+
--EXPECTF--
19+
object(Closure)#%d (3) {
20+
["name"]=>
21+
string(%d) "{closure:%s:%d}"
22+
["file"]=>
23+
string(%d) "%s"
24+
["line"]=>
25+
int(4)
26+
}
27+
called

0 commit comments

Comments
 (0)