diff --git a/Zend/tests/get_error_handler.phpt b/Zend/tests/get_error_handler.phpt index abe0b608a4cf..fb115eef9d78 100644 --- a/Zend/tests/get_error_handler.phpt +++ b/Zend/tests/get_error_handler.phpt @@ -8,7 +8,7 @@ class C { static function handleStatic() {} } -class Invokable { +class InvokableHandler { public function __invoke() { } } @@ -46,8 +46,8 @@ echo "\nClosure\n"; set_error_handler($f = function () {}); var_dump(get_error_handler() === $f); -echo "\nInvokable\n"; -set_error_handler($object = new Invokable()); +echo "\nInvokableHandler\n"; +set_error_handler($object = new InvokableHandler()); var_dump(get_error_handler() === $object); echo "\nStable return value\n"; @@ -80,7 +80,7 @@ bool(true) Closure bool(true) -Invokable +InvokableHandler bool(true) Stable return value diff --git a/Zend/tests/get_exception_handler.phpt b/Zend/tests/get_exception_handler.phpt index 05f43db2c0f4..cf4daf51ce72 100644 --- a/Zend/tests/get_exception_handler.phpt +++ b/Zend/tests/get_exception_handler.phpt @@ -8,7 +8,7 @@ class C { static function handleStatic() {} } -class Invokable { +class InvokableHandler { public function __invoke() { } } @@ -46,8 +46,8 @@ echo "\nClosure\n"; set_exception_handler($f = function () {}); var_dump(get_exception_handler() === $f); -echo "\nInvokable\n"; -set_exception_handler($object = new Invokable()); +echo "\nInvokableHandler\n"; +set_exception_handler($object = new InvokableHandler()); var_dump(get_exception_handler() === $object); echo "\nStable return value\n"; @@ -79,7 +79,7 @@ bool(true) Closure bool(true) -Invokable +InvokableHandler bool(true) Stable return value diff --git a/Zend/tests/magic_methods/invokable_abstract_class.phpt b/Zend/tests/magic_methods/invokable_abstract_class.phpt new file mode 100644 index 000000000000..5aed1b10c45e --- /dev/null +++ b/Zend/tests/magic_methods/invokable_abstract_class.phpt @@ -0,0 +1,45 @@ +--TEST-- +Abstract class can implement Invokable with abstract or concrete __invoke() +--FILE-- + +--EXPECT-- +bool(true) +int(10) +bool(true) +string(5) "HELLO" +bool(true) +string(8) "concrete" diff --git a/Zend/tests/magic_methods/invokable_anonymous_class.phpt b/Zend/tests/magic_methods/invokable_anonymous_class.phpt new file mode 100644 index 000000000000..1cab1d124d1e --- /dev/null +++ b/Zend/tests/magic_methods/invokable_anonymous_class.phpt @@ -0,0 +1,30 @@ +--TEST-- +Anonymous class with __invoke() auto-implements Invokable +--FILE-- + +--EXPECT-- +bool(true) +string(9) "anonymous" +bool(true) +int(42) diff --git a/Zend/tests/magic_methods/invokable_automatic_implementation.phpt b/Zend/tests/magic_methods/invokable_automatic_implementation.phpt new file mode 100644 index 000000000000..aa14bc753047 --- /dev/null +++ b/Zend/tests/magic_methods/invokable_automatic_implementation.phpt @@ -0,0 +1,75 @@ +--TEST-- +Invokable is automatically implemented +--FILE-- +getInterfaceNames()); + +/* Inheritance: child inherits Invokable from parent */ +class Child extends Test {} + +var_dump(new Child instanceof Invokable); +var_dump((new ReflectionClass(Child::class))->getInterfaceNames()); + +/* Child overrides __invoke */ +class ChildOverride extends Test { + public function __invoke(): string { + return "bar"; + } +} + +var_dump(new ChildOverride instanceof Invokable); + +/* Arbitrary signature: different params and return type */ +class Adder { + public function __invoke(int $a, int $b): int { + return $a + $b; + } +} + +var_dump(new Adder instanceof Invokable); + +/* No params, no return type */ +class NoSig { + public function __invoke() {} +} + +var_dump(new NoSig instanceof Invokable); + +/* Explicit + implicit: class has __invoke and writes implements Invokable */ +class ExplicitAndImplicit implements Invokable { + public function __invoke(): void {} +} + +var_dump(new ExplicitAndImplicit instanceof Invokable); +/* Should appear only once in the interface list */ +var_dump((new ReflectionClass(ExplicitAndImplicit::class))->getInterfaceNames()); + +?> +--EXPECT-- +bool(true) +array(1) { + [0]=> + string(9) "Invokable" +} +bool(true) +array(1) { + [0]=> + string(9) "Invokable" +} +bool(true) +bool(true) +bool(true) +bool(true) +array(1) { + [0]=> + string(9) "Invokable" +} diff --git a/Zend/tests/magic_methods/invokable_callable_covariance.phpt b/Zend/tests/magic_methods/invokable_callable_covariance.phpt new file mode 100644 index 000000000000..83bc07059079 --- /dev/null +++ b/Zend/tests/magic_methods/invokable_callable_covariance.phpt @@ -0,0 +1,65 @@ +--TEST-- +Invokable is covariant to callable in return types +--FILE-- + 1; + } +} + +/* Invokable is a valid covariant return type for callable */ +class Child extends Base { + public function getHandler(): Invokable { + return new class { + public function __invoke(): int { + return 2; + } + }; + } +} + +$c = new Child(); +$handler = $c->getHandler(); +var_dump($handler instanceof Invokable); +var_dump($handler()); + +/* Concrete class with __invoke is also covariant to callable */ +class Handler { + public function __invoke(): int { + return 3; + } +} + +class Child2 extends Base { + public function getHandler(): Handler { + return new Handler(); + } +} + +$c2 = new Child2(); +$handler2 = $c2->getHandler(); +var_dump($handler2 instanceof Invokable); +var_dump($handler2()); + +/* Closure is covariant to callable via Invokable */ +class Child3 extends Base { + public function getHandler(): Closure { + return fn() => 4; + } +} + +$c3 = new Child3(); +$handler3 = $c3->getHandler(); +var_dump($handler3 instanceof Invokable); +var_dump($handler3()); + +?> +--EXPECT-- +bool(true) +int(2) +bool(true) +int(3) +bool(true) +int(4) diff --git a/Zend/tests/magic_methods/invokable_closure.phpt b/Zend/tests/magic_methods/invokable_closure.phpt new file mode 100644 index 000000000000..7caec3aeccdc --- /dev/null +++ b/Zend/tests/magic_methods/invokable_closure.phpt @@ -0,0 +1,31 @@ +--TEST-- +Closure implements Invokable +--FILE-- + 1; +var_dump($fn instanceof Invokable); + +/* Anonymous function */ +$fn2 = function(int $x): int { return $x * 2; }; +var_dump($fn2 instanceof Invokable); + +/* Closure::fromCallable */ +$fn3 = Closure::fromCallable('strlen'); +var_dump($fn3 instanceof Invokable); + +/* First-class callable syntax */ +$fn4 = strlen(...); +var_dump($fn4 instanceof Invokable); + +/* Reflection confirms Closure implements Invokable */ +var_dump(in_array('Invokable', (new ReflectionClass(Closure::class))->getInterfaceNames())); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/Zend/tests/magic_methods/invokable_enum.phpt b/Zend/tests/magic_methods/invokable_enum.phpt new file mode 100644 index 000000000000..8e47f9886f5c --- /dev/null +++ b/Zend/tests/magic_methods/invokable_enum.phpt @@ -0,0 +1,31 @@ +--TEST-- +Enums with __invoke() auto-implement Invokable +--FILE-- +name; } +} + +var_dump(Color::Red instanceof Invokable); +var_dump((Color::Red)()); + +/* Enum explicitly implementing Invokable */ +enum Direction implements Invokable { + case Up; + case Down; + public function __invoke(): int { return $this === self::Up ? 1 : -1; } +} + +var_dump(Direction::Up instanceof Invokable); +var_dump((Direction::Up)()); + +?> +--EXPECT-- +bool(true) +string(3) "Red" +bool(true) +int(1) diff --git a/Zend/tests/magic_methods/invokable_enum_without_invoke.phpt b/Zend/tests/magic_methods/invokable_enum_without_invoke.phpt new file mode 100644 index 000000000000..897999773ab1 --- /dev/null +++ b/Zend/tests/magic_methods/invokable_enum_without_invoke.phpt @@ -0,0 +1,12 @@ +--TEST-- +Enum explicitly implementing Invokable without __invoke() causes fatal error +--FILE-- + +--EXPECTF-- +Fatal error: Enum Color must have an __invoke() method to implement Invokable in %s on line %d diff --git a/Zend/tests/magic_methods/invokable_explicit_without_invoke.phpt b/Zend/tests/magic_methods/invokable_explicit_without_invoke.phpt new file mode 100644 index 000000000000..4c3eacabb674 --- /dev/null +++ b/Zend/tests/magic_methods/invokable_explicit_without_invoke.phpt @@ -0,0 +1,10 @@ +--TEST-- +Explicit implements Invokable without __invoke() causes fatal error +--FILE-- + +--EXPECTF-- +Fatal error: Class Bad must have an __invoke() method to implement Invokable in %s on line %d diff --git a/Zend/tests/magic_methods/invokable_interface_extends.phpt b/Zend/tests/magic_methods/invokable_interface_extends.phpt new file mode 100644 index 000000000000..d1ba4158f24c --- /dev/null +++ b/Zend/tests/magic_methods/invokable_interface_extends.phpt @@ -0,0 +1,27 @@ +--TEST-- +Interface extending Invokable +--FILE-- +getInterfaceNames()); + +?> +--EXPECT-- +bool(true) +bool(true) +array(2) { + [0]=> + string(11) "MyInvokable" + [1]=> + string(9) "Invokable" +} diff --git a/Zend/tests/magic_methods/invokable_intersection_type.phpt b/Zend/tests/magic_methods/invokable_intersection_type.phpt new file mode 100644 index 000000000000..a357a8e1a6e9 --- /dev/null +++ b/Zend/tests/magic_methods/invokable_intersection_type.phpt @@ -0,0 +1,43 @@ +--TEST-- +Invokable in intersection types +--FILE-- +getLog() . "\n"; +} + +process(new LoggingHandler()); + +/* Object that is Invokable but not Loggable should be rejected */ +class PlainHandler { + public function __invoke(): string { + return "plain"; + } +} + +try { + process(new PlainHandler()); +} catch (TypeError $e) { + echo "TypeError caught\n"; +} + +?> +--EXPECT-- +result +log entry +TypeError caught diff --git a/Zend/tests/magic_methods/invokable_property_type.phpt b/Zend/tests/magic_methods/invokable_property_type.phpt new file mode 100644 index 000000000000..7b1ebeac933b --- /dev/null +++ b/Zend/tests/magic_methods/invokable_property_type.phpt @@ -0,0 +1,37 @@ +--TEST-- +Invokable can be used as a property type +--FILE-- +handler = new Handler(); +var_dump(($d->handler)(5)); + +/* Closure is also Invokable */ +$d->handler = fn(int $x): int => $x * 3; +var_dump(($d->handler)(5)); + +/* Non-invokable object should be rejected */ +try { + $d->handler = new stdClass(); +} catch (TypeError $e) { + echo $e->getMessage() . "\n"; +} + +?> +--EXPECTF-- +int(10) +int(15) +Cannot assign stdClass to property Dispatcher::$handler of type Invokable diff --git a/Zend/tests/magic_methods/invokable_trait.phpt b/Zend/tests/magic_methods/invokable_trait.phpt new file mode 100644 index 000000000000..28448f620b69 --- /dev/null +++ b/Zend/tests/magic_methods/invokable_trait.phpt @@ -0,0 +1,65 @@ +--TEST-- +Invokable is implicitly declared when __invoke() comes from a trait +--FILE-- +getInterfaceNames()); +$rc = new ReflectionClass(T2::class); +var_dump($rc->getInterfaceNames()); + +/* But the classes using the traits should have Invokable */ +$rc = new ReflectionClass(C::class); +var_dump($rc->getInterfaceNames()); +$rc = new ReflectionClass(C2::class); +var_dump($rc->getInterfaceNames()); + +/* Class that already has __invoke and uses a trait with __invoke */ +class CWithOwn { + use T; + public function __invoke(): string { + return "own"; + } +} + +var_dump(new CWithOwn instanceof Invokable); + +?> +--EXPECT-- +bool(true) +bool(true) +array(0) { +} +array(0) { +} +array(1) { + [0]=> + string(9) "Invokable" +} +array(1) { + [0]=> + string(9) "Invokable" +} +bool(true) diff --git a/Zend/tests/magic_methods/invokable_type_declarations.phpt b/Zend/tests/magic_methods/invokable_type_declarations.phpt new file mode 100644 index 000000000000..f42cb20f5645 --- /dev/null +++ b/Zend/tests/magic_methods/invokable_type_declarations.phpt @@ -0,0 +1,62 @@ +--TEST-- +Invokable in various type declaration contexts +--FILE-- + strtolower($s)); + +/* Return type */ +function getInvokable(): Invokable { + return new Handler(); +} +var_dump(getInvokable() instanceof Invokable); + +/* Nullable */ +function maybeInvokable(?Invokable $fn): void { + if ($fn !== null) { + echo $fn("world") . "\n"; + } else { + echo "null\n"; + } +} +maybeInvokable(new Handler()); +maybeInvokable(null); + +/* Union type */ +function invokableOrString(Invokable|string $val): void { + if ($val instanceof Invokable) { + echo $val("union") . "\n"; + } else { + echo $val . "\n"; + } +} +invokableOrString(new Handler()); +invokableOrString("plain string"); + +/* instanceof checks: negative */ +var_dump(new stdClass() instanceof Invokable); +var_dump("strlen" instanceof Invokable); + +?> +--EXPECT-- +HELLO +hello +bool(true) +WORLD +null +UNION +plain string +bool(false) +bool(false) diff --git a/Zend/tests/magic_methods/invokable_typed_interface.phpt b/Zend/tests/magic_methods/invokable_typed_interface.phpt new file mode 100644 index 000000000000..dc306a9f94e9 --- /dev/null +++ b/Zend/tests/magic_methods/invokable_typed_interface.phpt @@ -0,0 +1,53 @@ +--TEST-- +Interface extending Invokable can fix __invoke() signature +--FILE-- + $request]); + } +} + +$jh = new MyJsonHandler(); +var_dump($jh instanceof Invokable); +var_dump($jh instanceof RequestHandler); +var_dump($jh instanceof JsonHandler); +var_dump($jh('{"key":"value"}')); + +/* Can be used as parameter type with the fixed signature interface */ +function dispatch(RequestHandler $handler, string $req): string { + return $handler($req); +} + +echo dispatch(new MyHandler(), "hello") . "\n"; + +?> +--EXPECT-- +bool(true) +bool(true) +string(13) "handled: test" +bool(true) +bool(true) +bool(true) +string(33) "{"handled":"{\"key\":\"value\"}"}" +handled: hello diff --git a/Zend/tests/magic_methods/invokable_visibility.phpt b/Zend/tests/magic_methods/invokable_visibility.phpt new file mode 100644 index 000000000000..c9cd7396cf5b --- /dev/null +++ b/Zend/tests/magic_methods/invokable_visibility.phpt @@ -0,0 +1,25 @@ +--TEST-- +Non-public __invoke() still auto-implements Invokable (visibility is a separate warning) +--FILE-- + +--EXPECTF-- +Warning: The magic method ProtectedInvoke::__invoke() must have public visibility in %s on line %d + +Warning: The magic method PrivateInvoke::__invoke() must have public visibility in %s on line %d +bool(true) +bool(true) diff --git a/Zend/zend.h b/Zend/zend.h index ebb7d2391955..e13b38b2eccd 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -188,6 +188,7 @@ struct _zend_class_entry { zend_function *__debugInfo; zend_function *__serialize; zend_function *__unserialize; + zend_function *__invoke; const zend_object_handlers *default_object_handlers; diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 211a5d14e6c3..9de8e1a47388 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2889,6 +2889,8 @@ ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, c ce->__callstatic = fptr; } else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME)) { ce->__tostring = fptr; + } else if (zend_string_equals_literal(lcname, ZEND_INVOKE_FUNC_NAME)) { + ce->__invoke = fptr; } else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) { ce->__debugInfo = fptr; ce->ce_flags |= ZEND_ACC_USE_GUARDS; @@ -3518,6 +3520,12 @@ static zend_class_entry *do_register_internal_class(const zend_class_entry *orig && "Should be registered before first class using __toString()"); zend_do_implement_interface(class_entry, zend_ce_stringable); } + if (class_entry->__invoke && !zend_string_equals_literal(class_entry->name, "Invokable") + && !(class_entry->ce_flags & ZEND_ACC_TRAIT)) { + ZEND_ASSERT(zend_ce_invokable + && "Should be registered before first class using __invoke()"); + zend_do_implement_interface(class_entry, zend_ce_invokable); + } return class_entry; } /* }}} */ @@ -3562,6 +3570,12 @@ ZEND_API void zend_class_implements(zend_class_entry *class_entry, int num_inter * silently ignore an explicit implementation. */ continue; } + if (interface_entry == zend_ce_invokable + && zend_class_implements_interface(class_entry, zend_ce_invokable)) { + /* Invokable is implemented automatically, + * silently ignore an explicit implementation. */ + continue; + } zend_do_implement_interface(class_entry, interface_entry); } diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index cca69985a0df..574d51fa6912 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -730,7 +730,7 @@ ZEND_COLD ZEND_METHOD(Closure, __construct) void zend_register_closure_ce(void) /* {{{ */ { - zend_ce_closure = register_class_Closure(); + zend_ce_closure = register_class_Closure(zend_ce_invokable); zend_ce_closure->create_object = zend_closure_new; zend_ce_closure->default_object_handlers = &closure_handlers; diff --git a/Zend/zend_closures.stub.php b/Zend/zend_closures.stub.php index 46b51617eef9..2100cefc3c70 100644 --- a/Zend/zend_closures.stub.php +++ b/Zend/zend_closures.stub.php @@ -6,7 +6,7 @@ * @strict-properties * @not-serializable */ -final class Closure +final class Closure implements Invokable { private function __construct() {} diff --git a/Zend/zend_closures_arginfo.h b/Zend/zend_closures_arginfo.h index 5bc983a97c2c..c2d657e697a3 100644 --- a/Zend/zend_closures_arginfo.h +++ b/Zend/zend_closures_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit zend_closures.stub.php instead. - * Stub hash: e0626e52adb2d38dad1140c1a28cc7774cc84500 */ + * Stub hash: 57f09f1725ded63cd58f37977a0572dd5e2394bc */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Closure___construct, 0, 0, 0) ZEND_END_ARG_INFO() @@ -44,12 +44,13 @@ static const zend_function_entry class_Closure_methods[] = { ZEND_FE_END }; -static zend_class_entry *register_class_Closure(void) +static zend_class_entry *register_class_Closure(zend_class_entry *class_entry_Invokable) { zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "Closure", class_Closure_methods); class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE); + zend_class_implements(class_entry, 1, class_entry_Invokable); return class_entry; } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ee83ee75ff6e..63af34c09f57 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2100,6 +2100,7 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_hand ce->__call = NULL; ce->__callstatic = NULL; ce->__tostring = NULL; + ce->__invoke = NULL; ce->__serialize = NULL; ce->__unserialize = NULL; ce->__debugInfo = NULL; @@ -8534,6 +8535,23 @@ static void add_stringable_interface(zend_class_entry *ce) { ZSTR_INIT_LITERAL("stringable", 0); } +static void add_invokable_interface(zend_class_entry *ce) { + for (uint32_t i = 0; i < ce->num_interfaces; i++) { + if (zend_string_equals_literal(ce->interface_names[i].lc_name, "invokable")) { + /* Interface already explicitly implemented */ + return; + } + } + + ce->num_interfaces++; + ce->interface_names = + erealloc(ce->interface_names, sizeof(zend_class_name) * ce->num_interfaces); + ce->interface_names[ce->num_interfaces - 1].name = + ZSTR_INIT_LITERAL("Invokable", 0); + ce->interface_names[ce->num_interfaces - 1].lc_name = + ZSTR_INIT_LITERAL("invokable", 0); +} + static zend_string *zend_begin_method_decl(zend_op_array *op_array, zend_string *name, bool has_body) /* {{{ */ { zend_class_entry *ce = CG(active_class_entry); @@ -8611,6 +8629,10 @@ static zend_string *zend_begin_method_decl(zend_op_array *op_array, zend_string && !(ce->ce_flags & ZEND_ACC_TRAIT)) { add_stringable_interface(ce); } + if (zend_string_equals_literal(lcname, ZEND_INVOKE_FUNC_NAME) + && !(ce->ce_flags & ZEND_ACC_TRAIT)) { + add_invokable_interface(ce); + } return lcname; } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index bfa709ba60b6..f06fc38f6662 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -491,12 +491,13 @@ static inheritance_status zend_is_class_subtype_of_type( } } - /* If the parent has 'callable' as a return type, then Closure satisfies the co-variant check */ + /* If the parent has 'callable' as a return type, then Invokable satisfies the co-variant check */ if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_CALLABLE) { if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name); if (!fe_ce) { have_unresolved = true; - } else if (fe_ce == zend_ce_closure) { + } else if (fe_ce == zend_ce_invokable + || zend_class_implements_interface(fe_ce, zend_ce_invokable)) { track_class_dependency(fe_ce, fe_class_name); return INHERITANCE_SUCCESS; } @@ -3724,6 +3725,19 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string do_interface_implementation(ce, zend_ce_stringable); } + /* Normally Invokable is added during compilation. However, if it is imported from a trait, + * we need to explicitly add the interface here. */ + if (ce->__invoke && !(ce->ce_flags & ZEND_ACC_TRAIT) + && !zend_class_implements_interface(ce, zend_ce_invokable)) { + ZEND_ASSERT(ce->__invoke->common.fn_flags & ZEND_ACC_TRAIT_CLONE); + ce->ce_flags |= ZEND_ACC_RESOLVED_INTERFACES; + ce->num_interfaces++; + ce->interfaces = perealloc(ce->interfaces, + sizeof(zend_class_entry *) * ce->num_interfaces, ce->type == ZEND_INTERNAL_CLASS); + ce->interfaces[ce->num_interfaces - 1] = zend_ce_invokable; + do_interface_implementation(ce, zend_ce_invokable); + } + zend_build_properties_info_table(ce); } zend_catch { /* Do not leak recorded errors to the next linked class. */ diff --git a/Zend/zend_interfaces.c b/Zend/zend_interfaces.c index 404dd9db893e..5498c322857d 100644 --- a/Zend/zend_interfaces.c +++ b/Zend/zend_interfaces.c @@ -19,6 +19,7 @@ #include "zend.h" #include "zend_API.h" #include "zend_interfaces.h" +#include "zend_closures.h" #include "zend_exceptions.h" #include "zend_interfaces_arginfo.h" #include "zend_property_hooks.h" @@ -30,6 +31,7 @@ ZEND_API zend_class_entry *zend_ce_arrayaccess; ZEND_API zend_class_entry *zend_ce_serializable; ZEND_API zend_class_entry *zend_ce_countable; ZEND_API zend_class_entry *zend_ce_stringable; +ZEND_API zend_class_entry *zend_ce_invokable; ZEND_API zend_class_entry *zend_ce_internal_iterator; static zend_object_handlers zend_internal_iterator_handlers; @@ -646,6 +648,28 @@ ZEND_METHOD(InternalIterator, rewind) { intern->iter->index = 0; } +/* {{{ zend_implement_invokable */ +static int zend_implement_invokable(zend_class_entry *interface, zend_class_entry *class_type) +{ + if (class_type->ce_flags & (ZEND_ACC_INTERFACE | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) { + return SUCCESS; + } + + /* Closure provides __invoke dynamically per-instance via get_method handler, + * so it does not appear in the function table at registration time. */ + if (class_type->__invoke + || zend_hash_str_exists(&class_type->function_table, ZEND_INVOKE_FUNC_NAME, sizeof(ZEND_INVOKE_FUNC_NAME) - 1) + || zend_string_equals_literal(class_type->name, "Closure")) { + return SUCCESS; + } + + zend_error_noreturn(E_CORE_ERROR, "%s %s must have an __invoke() method to implement Invokable", + zend_get_object_type_uc(class_type), + ZSTR_VAL(class_type->name)); + return FAILURE; +} +/* }}} */ + /* {{{ zend_register_interfaces */ ZEND_API void zend_register_interfaces(void) { @@ -668,6 +692,9 @@ ZEND_API void zend_register_interfaces(void) zend_ce_stringable = register_class_Stringable(); + zend_ce_invokable = register_class_Invokable(); + zend_ce_invokable->interface_gets_implemented = zend_implement_invokable; + zend_ce_internal_iterator = register_class_InternalIterator(zend_ce_iterator); zend_ce_internal_iterator->create_object = zend_internal_iterator_create; zend_ce_internal_iterator->default_object_handlers = &zend_internal_iterator_handlers; diff --git a/Zend/zend_interfaces.h b/Zend/zend_interfaces.h index 883e482f510c..1862d08f0ba5 100644 --- a/Zend/zend_interfaces.h +++ b/Zend/zend_interfaces.h @@ -31,6 +31,7 @@ extern ZEND_API zend_class_entry *zend_ce_arrayaccess; extern ZEND_API zend_class_entry *zend_ce_serializable; extern ZEND_API zend_class_entry *zend_ce_countable; extern ZEND_API zend_class_entry *zend_ce_stringable; +extern ZEND_API zend_class_entry *zend_ce_invokable; typedef struct _zend_user_iterator { zend_object_iterator it; diff --git a/Zend/zend_interfaces.stub.php b/Zend/zend_interfaces.stub.php index 2247205e4697..4dfe2eb698cc 100644 --- a/Zend/zend_interfaces.stub.php +++ b/Zend/zend_interfaces.stub.php @@ -66,6 +66,10 @@ interface Stringable public function __toString(): string; } +interface Invokable +{ +} + /** * @not-serializable */ diff --git a/Zend/zend_interfaces_arginfo.h b/Zend/zend_interfaces_arginfo.h index 836313e4f41b..6dbcef8daad5 100644 --- a/Zend/zend_interfaces_arginfo.h +++ b/Zend/zend_interfaces_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit zend_interfaces.stub.php instead. - * Stub hash: a9c915c11e5989d8c7cf2d704ada09ca765670c3 */ + * Stub hash: c9744e70f6ebf8006926b777243891db07625111 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX(arginfo_class_IteratorAggregate_getIterator, 0, 0, Traversable, 0) ZEND_END_ARG_INFO() @@ -189,6 +189,16 @@ static zend_class_entry *register_class_Stringable(void) return class_entry; } +static zend_class_entry *register_class_Invokable(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "Invokable", NULL); + class_entry = zend_register_internal_interface(&ce); + + return class_entry; +} + static zend_class_entry *register_class_InternalIterator(zend_class_entry *class_entry_Iterator) { zend_class_entry ce, *class_entry;