Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ PHP NEWS
. Fixed bug GH-16665 (\array and \callable should not be usable in
class_alias). (nielsdos)
. Added PHP_BUILD_DATE constant. (cmb)
. Added support for Closures in constant expressions. (timwolla,
Volker Dusch)
. Added support for Closures and first class callables in constant
expressions. (timwolla, Volker Dusch)
. Use `clock_gettime_nsec_np()` for high resolution timer on macOS
if available. (timwolla)
. Implement GH-15680 (Enhance zend_dump_op_array to properly represent
Expand Down
4 changes: 3 additions & 1 deletion UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,10 @@ PHP 8.5 UPGRADE NOTES

- Core:
. Closure is now a proper subtype of callable
. Added support for Closures in constant expressions.
. Added support for Closures and first class callables in constant
expressions.
RFC: https://wiki.php.net/rfc/closures_in_const_expr
RFC: https://wiki.php.net/rfc/fcc_in_const_expr
. Fatal Errors (such as an exceeded maximum execution time) now include a
backtrace.
RFC: https://wiki.php.net/rfc/error_backtraces_v2
Expand Down
50 changes: 50 additions & 0 deletions Zend/tests/first_class_callable/constexpr/attributes.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
--TEST--
Allow defining FCC in attributes
--EXTENSIONS--
reflection
--FILE--
<?php

#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Attr {
public function __construct(public Closure $value) {
var_dump($value('abc'));
}
}

#[Attr(strrev(...))]
#[Attr(strlen(...))]
class C {}

foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
var_dump($reflectionAttribute->newInstance());
}

?>
--EXPECTF--
string(3) "cba"
object(Attr)#%d (1) {
["value"]=>
object(Closure)#%d (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
}
int(3)
object(Attr)#%d (1) {
["value"]=>
object(Closure)#%d (2) {
["function"]=>
string(6) "strlen"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
--TEST--
AST printing for FCC in attributes
--FILE--
<?php

// Do not use `false &&` to fully evaluate the function / class definition.

try {
\assert(
!
#[Attr(strrev(...))]
function () { }
);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

try {
\assert(
!
new #[Attr(strrev(...))]
class {}
);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
assert(!#[Attr(strrev(...))] function () {
})
assert(!new #[Attr(strrev(...))] class {
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
AST printing for FCC in attributes at runtime
--FILE--
<?php

namespace Test;

class Clazz {
#[Attr(strrev(...), \strrev(...), Clazz::foo(...), self::foo(...))]
function foo() { }
}

$r = new \ReflectionMethod(Clazz::class, 'foo');
foreach ($r->getAttributes() as $attribute) {
echo $attribute;
}

?>
--EXPECT--
Attribute [ Test\Attr ] {
- Arguments [4] {
Argument #0 [ Test\strrev(...) ]
Argument #1 [ \strrev(...) ]
Argument #2 [ \Test\Clazz::foo(...) ]
Argument #3 [ self::foo(...) ]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
FCC in attribute may access private methods
--EXTENSIONS--
reflection
--FILE--
<?php

#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Attr {
public function __construct(public Closure $value) {}
}

#[Attr(C::myMethod(...))]
class C {
private static function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}

foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
($reflectionAttribute->newInstance()->value)('abc');
}

?>
--EXPECT--
Called C::myMethod
string(3) "abc"
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
FCC in attribute may not access unrelated private methods
--EXTENSIONS--
reflection
--FILE--
<?php

#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Attr {
public function __construct(public Closure $value) {}
}

class E {
private static function myMethod(string $foo) {
echo "Called ", __METHOD__, PHP_EOL;
var_dump($foo);
}
}

#[Attr(E::myMethod(...))]
class C {
}

foreach ((new ReflectionClass(C::class))->getAttributes() as $reflectionAttribute) {
($reflectionAttribute->newInstance()->value)('abc');
}

?>
--EXPECTF--
Fatal error: Uncaught Error: Call to private method E::myMethod() from scope C in %s:%d
Stack trace:
#0 %s(%d): ReflectionAttribute->newInstance()
#1 {main}
thrown in %s on line %d
31 changes: 31 additions & 0 deletions Zend/tests/first_class_callable/constexpr/autoload.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
FCC in const expression triggers autoloader.
--FILE--
<?php

spl_autoload_register(static function ($class) {
echo "Autoloading {$class}", PHP_EOL;
eval(
<<<'EOT'
class AutoloadedClass {
public static function withStaticMethod() {
echo "Called ", __METHOD__, PHP_EOL;
}
}
EOT
);
});

const Closure = AutoloadedClass::withStaticMethod(...);

var_dump(Closure);
(Closure)();

?>
--EXPECTF--
Autoloading AutoloadedClass
object(Closure)#%d (1) {
["function"]=>
string(16) "withStaticMethod"
}
Called AutoloadedClass::withStaticMethod
22 changes: 22 additions & 0 deletions Zend/tests/first_class_callable/constexpr/basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
Allow defining FCC in const expressions.
--FILE--
<?php

const Closure = strrev(...);

var_dump(Closure);
var_dump((Closure)("abc"));

?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(%d) "%s"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"
22 changes: 22 additions & 0 deletions Zend/tests/first_class_callable/constexpr/case_insensitive.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
Allow defining FCC in const expressions with case-insensitive names.
--FILE--
<?php

const Closure = StrRev(...);

var_dump(Closure);
var_dump((Closure)("abc"));

?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(%d) "%s"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"
24 changes: 24 additions & 0 deletions Zend/tests/first_class_callable/constexpr/class_const.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
Allow defining FCC in class constants.
--FILE--
<?php

class C {
const Closure = strrev(...);
}

var_dump(C::Closure);
var_dump((C::Closure)("abc"));

?>
--EXPECTF--
object(Closure)#%d (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
string(3) "cba"
39 changes: 39 additions & 0 deletions Zend/tests/first_class_callable/constexpr/complex_array.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
--TEST--
Allow defining FCC wrapped in an array in const expressions.
--FILE--
<?php

const Closure = [strrev(...), strlen(...)];

var_dump(Closure);

foreach (Closure as $closure) {
var_dump($closure("abc"));
}

?>
--EXPECTF--
array(2) {
[0]=>
object(Closure)#%d (2) {
["function"]=>
string(6) "strrev"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
[1]=>
object(Closure)#%d (2) {
["function"]=>
string(6) "strlen"
["parameter"]=>
array(1) {
["$string"]=>
string(10) "<required>"
}
}
}
string(3) "cba"
int(3)
18 changes: 18 additions & 0 deletions Zend/tests/first_class_callable/constexpr/default_args.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
FCC in default argument
--FILE--
<?php

function test(
Closure $name = strrev(...)
) {
var_dump($name("abc"));
}

test();
test(strlen(...));

?>
--EXPECT--
string(3) "cba"
int(3)
20 changes: 20 additions & 0 deletions Zend/tests/first_class_callable/constexpr/error_abstract.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
FCC in initializer errors for FCC on abstract method
--FILE--
<?php

abstract class Foo {
abstract public static function myMethod(string $foo);
}

const Closure = Foo::myMethod(...);

var_dump(Closure);
(Closure)("abc");

?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot call abstract method Foo::myMethod() in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d
12 changes: 12 additions & 0 deletions Zend/tests/first_class_callable/constexpr/error_dynamic_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
FCC in initializer errors for FCC on variable.
--FILE--
<?php

const Closure = $foo(...);

var_dump(Closure);

?>
--EXPECTF--
Fatal error: Cannot use dynamic function name in constant expression in %s on line %d
Loading
Loading