Skip to content

Commit 61e1f8a

Browse files
committed
Let closure created from magic method accept named parameters
Implements GH-11348. Closes GH-11364.
1 parent 16a63d7 commit 61e1f8a

File tree

4 files changed

+128
-2
lines changed

4 files changed

+128
-2
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ PHP NEWS
4040
. Fix bug #79836 (Segfault in concat_function). (nielsdos)
4141
. Fix bug #81705 (type confusion/UAF on set_error_handler with concat
4242
operation). (nielsdos)
43+
. Fix GH-11348 (Closure created from magic method does not accept named
44+
arguments). (nielsdos)
4345

4446
- Date:
4547
. Implement More Appropriate Date/Time Exceptions RFC. (Derick)

UPGRADING

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ PHP 8.3 UPGRADE NOTES
5858
RFC: https://wiki.php.net/rfc/readonly_amendments
5959
. Class, interface, trait, and enum constants now support type
6060
declarations. RFC: https://wiki.php.net/rfc/typed_class_constants
61+
. Closures created from magic methods can now accept named arguments.
6162

6263
- Posix
6364
. posix_getrlimit() now takes an optional $res parameter to allow fetching a
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
--TEST--
2+
Trampoline closure created from magic method accepts named arguments
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public function __call($name, $args) {
8+
var_dump($name, $args);
9+
}
10+
public static function __callStatic($name, $args) {
11+
var_dump($name, $args);
12+
}
13+
}
14+
15+
$test = new Test;
16+
17+
echo "-- Non-static cases --\n";
18+
$test->test(1, 2, a: 123);
19+
$test->test(...)(1, 2);
20+
$test->test(...)(1, 2, a: 123, b: $test);
21+
$test->test(...)(a: 123, b: $test);
22+
$test->test(...)();
23+
24+
echo "-- Static cases --\n";
25+
Test::testStatic(1, 2, a: 123);
26+
Test::testStatic(...)(1, 2);
27+
Test::testStatic(...)(1, 2, a: 123, b: $test);
28+
Test::testStatic(...)(a: 123, b: $test);
29+
Test::testStatic(...)();
30+
31+
?>
32+
--EXPECT--
33+
-- Non-static cases --
34+
string(4) "test"
35+
array(3) {
36+
[0]=>
37+
int(1)
38+
[1]=>
39+
int(2)
40+
["a"]=>
41+
int(123)
42+
}
43+
string(4) "test"
44+
array(2) {
45+
[0]=>
46+
int(1)
47+
[1]=>
48+
int(2)
49+
}
50+
string(4) "test"
51+
array(4) {
52+
[0]=>
53+
int(1)
54+
[1]=>
55+
int(2)
56+
["a"]=>
57+
int(123)
58+
["b"]=>
59+
object(Test)#1 (0) {
60+
}
61+
}
62+
string(4) "test"
63+
array(2) {
64+
["a"]=>
65+
int(123)
66+
["b"]=>
67+
object(Test)#1 (0) {
68+
}
69+
}
70+
string(4) "test"
71+
array(0) {
72+
}
73+
-- Static cases --
74+
string(10) "testStatic"
75+
array(3) {
76+
[0]=>
77+
int(1)
78+
[1]=>
79+
int(2)
80+
["a"]=>
81+
int(123)
82+
}
83+
string(10) "testStatic"
84+
array(2) {
85+
[0]=>
86+
int(1)
87+
[1]=>
88+
int(2)
89+
}
90+
string(10) "testStatic"
91+
array(4) {
92+
[0]=>
93+
int(1)
94+
[1]=>
95+
int(2)
96+
["a"]=>
97+
int(123)
98+
["b"]=>
99+
object(Test)#1 (0) {
100+
}
101+
}
102+
string(10) "testStatic"
103+
array(2) {
104+
["a"]=>
105+
int(123)
106+
["b"]=>
107+
object(Test)#1 (0) {
108+
}
109+
}
110+
string(10) "testStatic"
111+
array(0) {
112+
}

Zend/zend_closures.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,18 @@ static ZEND_NAMED_FUNCTION(zend_closure_call_magic) /* {{{ */ {
294294
fci.params = params;
295295
fci.param_count = 2;
296296
ZVAL_STR(&fci.params[0], EX(func)->common.function_name);
297-
if (ZEND_NUM_ARGS()) {
297+
if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
298+
zend_string *name;
299+
zval *named_param_zval;
300+
array_init_size(&fci.params[1], ZEND_NUM_ARGS() + zend_hash_num_elements(EX(extra_named_params)));
301+
/* Avoid conversion from packed to mixed later. */
302+
zend_hash_real_init_mixed(Z_ARRVAL(fci.params[1]));
303+
zend_copy_parameters_array(ZEND_NUM_ARGS(), &fci.params[1]);
304+
ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(EX(extra_named_params), name, named_param_zval) {
305+
Z_TRY_ADDREF_P(named_param_zval);
306+
zend_hash_add_new(Z_ARRVAL(fci.params[1]), name, named_param_zval);
307+
} ZEND_HASH_FOREACH_END();
308+
} else if (ZEND_NUM_ARGS()) {
298309
array_init_size(&fci.params[1], ZEND_NUM_ARGS());
299310
zend_copy_parameters_array(ZEND_NUM_ARGS(), &fci.params[1]);
300311
} else {
@@ -841,7 +852,7 @@ void zend_closure_from_frame(zval *return_value, zend_execute_data *call) { /* {
841852

842853
memset(&trampoline, 0, sizeof(zend_internal_function));
843854
trampoline.type = ZEND_INTERNAL_FUNCTION;
844-
trampoline.fn_flags = mptr->common.fn_flags & ZEND_ACC_STATIC;
855+
trampoline.fn_flags = mptr->common.fn_flags & (ZEND_ACC_STATIC | ZEND_ACC_VARIADIC);
845856
trampoline.handler = zend_closure_call_magic;
846857
trampoline.function_name = mptr->common.function_name;
847858
trampoline.scope = mptr->common.scope;

0 commit comments

Comments
 (0)