From f7b047b69f76b45842cb4301dc03e540dc52bd8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Fri, 11 Sep 2026 10:20:09 +0200 Subject: [PATCH] cli: Automatically execute Closures returned from primary script --- sapi/cli/php_cli.c | 17 ++++++++++++++++- sapi/cli/tests/primary_script_closure.inc | 7 +++++++ sapi/cli/tests/primary_script_closure_001.phpt | 16 ++++++++++++++++ sapi/cli/tests/primary_script_closure_002.phpt | 18 ++++++++++++++++++ sapi/cli/tests/primary_script_closure_003.phpt | 14 ++++++++++++++ sapi/cli/tests/primary_script_closure_004.phpt | 13 +++++++++++++ 6 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 sapi/cli/tests/primary_script_closure.inc create mode 100644 sapi/cli/tests/primary_script_closure_001.phpt create mode 100644 sapi/cli/tests/primary_script_closure_002.phpt create mode 100644 sapi/cli/tests/primary_script_closure_003.phpt create mode 100644 sapi/cli/tests/primary_script_closure_004.phpt diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c index 296b7cd521ff..40e1473e57eb 100644 --- a/sapi/cli/php_cli.c +++ b/sapi/cli/php_cli.c @@ -23,6 +23,7 @@ #include "zend_hash.h" #include "zend_modules.h" #include "zend_interfaces.h" +#include "zend_closures.h" #include "ext/reflection/php_reflection.h" @@ -930,7 +931,21 @@ static int do_cli(int argc, char **argv) /* {{{ */ if (interactive) { EG(exit_status) = cli_shell_callbacks.cli_shell_run(); } else { - php_execute_script(&file_handle); + zval retval; + ZVAL_UNDEF(&retval); + php_execute_script_ex(&file_handle, &retval); + + /* Check if the primary script returned a Closure and execute it: This allows + * a script to act both as a library and as an executable when executed directly. + */ + if (Z_TYPE(retval) == IS_OBJECT && Z_OBJCE(retval) == zend_ce_closure) { + zend_fcall_info_cache fcc = {0}; + Z_OBJ_HANDLER(retval, get_closure)(Z_OBJ(retval), &fcc.calling_scope, &fcc.function_handler, &fcc.object, /* check_only */ false); + fcc.called_scope = fcc.calling_scope; + fcc.closure = Z_OBJ(retval); + zend_call_known_fcc(&fcc, /* retval */ NULL, /* param_count */ 0, /* params */ NULL, /* named_params */ NULL); + } + zval_ptr_dtor(&retval); } break; case PHP_CLI_MODE_LINT: diff --git a/sapi/cli/tests/primary_script_closure.inc b/sapi/cli/tests/primary_script_closure.inc new file mode 100644 index 000000000000..ab84c98903fa --- /dev/null +++ b/sapi/cli/tests/primary_script_closure.inc @@ -0,0 +1,7 @@ + diff --git a/sapi/cli/tests/primary_script_closure_001.phpt b/sapi/cli/tests/primary_script_closure_001.phpt new file mode 100644 index 000000000000..b57fec7e5058 --- /dev/null +++ b/sapi/cli/tests/primary_script_closure_001.phpt @@ -0,0 +1,16 @@ +--TEST-- +Execute closures returned by the primary script +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Called diff --git a/sapi/cli/tests/primary_script_closure_002.phpt b/sapi/cli/tests/primary_script_closure_002.phpt new file mode 100644 index 000000000000..b22fda038f20 --- /dev/null +++ b/sapi/cli/tests/primary_script_closure_002.phpt @@ -0,0 +1,18 @@ +--TEST-- +Execute closures returned by the primary script (First Class Callable) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Called diff --git a/sapi/cli/tests/primary_script_closure_003.phpt b/sapi/cli/tests/primary_script_closure_003.phpt new file mode 100644 index 000000000000..22798134db44 --- /dev/null +++ b/sapi/cli/tests/primary_script_closure_003.phpt @@ -0,0 +1,14 @@ +--TEST-- +Execute closures returned by the primary script (From different file) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Called diff --git a/sapi/cli/tests/primary_script_closure_004.phpt b/sapi/cli/tests/primary_script_closure_004.phpt new file mode 100644 index 000000000000..94a656703760 --- /dev/null +++ b/sapi/cli/tests/primary_script_closure_004.phpt @@ -0,0 +1,13 @@ +--TEST-- +Execute closures returned by the primary script (Included file returns closure, but primary script does not) +--SKIPIF-- + +--FILE-- + +--EXPECT--