[php-src] master: docs: write docs for FCI/FCC structs and PHP callables API. (#23663)

From: Date: Sun, 11 Oct 2026 22:04:31 +0000
Subject: [php-src] master: docs: write docs for FCI/FCC structs and PHP callables API. (#23663)
Groups: php.cvs 
Request: Send a blank email to php-cvs+get-142192@lists.php.net to get a copy of this message
Author: Gina Peter Banyard (Girgias) Committer: GitHub (web-flow) Pusher: Girgias Date: 2026-10-11T23:04:28+01:00 Commit: https://github.com/php/php-src/commit/4a41768eec95b65ee4374dcb19583f6207fc0dd8 Raw diff: https://github.com/php/php-src/commit/4a41768eec95b65ee4374dcb19583f6207fc0dd8.diff docs: write docs for FCI/FCC structs and PHP callables API. (#23663) Partly based on my own writing for the PHP Internals Book: - https://github.com/phpinternalsbook/PHP-Internals-Book/pull/135 - https://github.com/phpinternalsbook/PHP-Internals-Book/pull/137 - https://github.com/phpinternalsbook/PHP-Internals-Book/pull/146 Co-authored-by: Arnaud Le Blanc <arnaud.lb@gmail.com> Changed paths: A docs/source/core/data-structures/fci-fcc.rst M docs/source/core/data-structures/index.rst Diff: diff --git a/docs/source/core/data-structures/fci-fcc.rst b/docs/source/core/data-structures/fci-fcc.rst new file mode 100644 index 000000000000..2d87fde79fe7 --- /dev/null +++ b/docs/source/core/data-structures/fci-fcc.rst @@ -0,0 +1,586 @@ +####################################################### + PHP Callables (zend_fcall_info/zend_fcall_info_cache) +####################################################### + +A common pattern for custom behaviour is to accept a user callback. As such we need a way to +represent PHP callables in C. For this purpose there exists two structures `zend_fcall_info` and +`zend_fcall_info_cache. zend_fcall_info_cache` is a pure representation of a PHP callable, and +should be the structure used to store PHP callables. `zend_fcall_info` contains all the required +information to *perform* a call to a PHP callable. We will use the commonly used abbreviation of FCI +and FCC when talking about `zend_fcall_info and zend_fcall_info_cache` respectively. The most +likely way to encounter those structs is by using the ZPP `f` argument specifier. + +********************** + Generic callable API +********************** + +There is a set of common parameters used within the callable APIs which are: + +.. list-table:: Common callable API parameter meaning + :header-rows: 1 + + - - Name + - Type + - Description + + - - `fn` + - `zend_function*` + - The function pointer to call, usually stored in `EG(function_table)` for global functions + or `zend_class_entry->function_table` for class methods. + + - - `this_ptr` + - `zend_object*` + - Object pointer representing `$this`. + + - - `called_scope` + - `zend_class_entry*` + - Scope where the method is called, `NULL` for global functions. + + - - `retval` + - `zval*` + - zval pointer where the return value of the called function will be stored. + + - - `param_count` + - `uint32_t` + - Number of parameters passed to the function via the `params` argument. + + - - `params` + - `zval*` + - Pointer to a C zval array containing the arguments to the function, may be `NULL` if + `param_count` is 0. + + - - `named_params` + + - `HashTable*` + + - HashTable containing named argument as name=>val, can also contain positional argument if + the key is numeric (in which case the positional argument is pushed as the next argument + regardless of its index) + + - - `consumed_args` + - `uint32_t` + - Arguments consumed by the call. See `zend_fcall_info`. + +There are some functions that do not require the use of either the FCI or the FCC structs: + +.. list-table:: Basic callable API + :header-rows: 1 + + - - Function/Macro + - Description + + - - .. code:: c + + bool zend_is_callable( + const zval *callable, + zend_fcall_info_cache *fcc, + char **error + ) + + - Check if the `callable` zval is a PHP callable. Returns true if it is, false otherwise. + `fcc is an out parameter constructing the FCC struct, see below for details. error` + is an optional out parameter holding the error message if `callable` is not a PHP + callable. Note: the signature used to be ``bool zend_is_callable(zval *callable, uint32_t + check_flags, zend_string **callable_name)`` prior to PHP 8.7. + + - - .. code:: c + + bool zend_is_callable_ex( + zval *callable, + zend_object *object, + uint32_t check_flags, + zend_string **callable_name, + zend_fcall_info_cache *fcc, + char **error + ) + + - Same as `zend_is_callable() with additional arguments. object if callable` is a + string, this `zend_object*` represents the class instance to check if such a method + exists. `check_flags is a bit mask of IS_CALLABLE_CHECK_SYNTAX_ONLY` and + `IS_CALLABLE_SUPPRESS_DEPRECATIONS, generally should be left to 0. callable_name` is + an optional out parameter, it will be set to the string representation of the `callable` + zval, set even in case of failure. + + - - .. code:: c + + zend_call_known_function( + zend_function *fn, + zend_object *this_ptr, + zend_class_entry *called_scope, + zval *retval, + uint32_t param_count, + zval *params, + HashTable *named_params + ) + + - Call a known function with given parameters. `retval may be NULL` in which case the + return value of the function is discarded. If `this_ptr is NULL` it must be a global + function or static method. `called_scope must *not* be NULL if fn` is a method + (instanced or static). + + - - .. code:: c + + zend_call_known_function_ex( + zend_function *fn, + zend_object *this_ptr, + zend_class_entry *called_scope, + zval *retval, + uint32_t param_count, + zval *params, + HashTable *named_params + uint32_t consumed_args + ) + + - Same as `zend_call_known_function(), with an additional consumed_args` parameter (see + `zend_fcall_info`). + + - - .. code:: c + + zend_call_known_instance_method( + zend_function *fn, + zend_object *this_ptr, + zval *retval, + uint32_t param_count, + zval *params + ) + + - Same as `zend_call_known_function(), where the called_scope` is derived from + `this_ptr->ce`. + + - - .. code:: c + + zend_call_known_instance_method_with_0_params( + zend_function *fn, + zend_object *this_ptr, + zval *retval, + ) + + - Helper function around `zend_call_known_instance_method()`. + + - - .. code:: c + + zend_call_known_instance_method_with_1_params( + zend_function *fn, + zend_object *this_ptr, + zval *retval, + zval *param + ) + + - Helper function around `zend_call_known_instance_method()`. + + - - .. code:: c + + zend_call_known_instance_method_with_2_params( + zend_function *fn, + zend_object *this_ptr, + zval *retval, + zval *param1, + zval *param2 + ) + + - Helper function around `zend_call_known_instance_method()`. + + - - .. code:: c + + zend_call_method_if_exists( + zend_object *this_ptr, + zend_string *method_name, + zval *retval, + uint32_t param_count, + zval *params + ) + + - Returns `FAILURE if the method_name does not exists on the object, SUCCESS` + otherwise. The retval *will* be UNDEF if the return value is `FAILURE``, as such the + retval can be unconditionally destroyed. However it may also be UNDEF in an exception was + thrown. + + - - .. code:: c + + call_user_function( + function_table, + object, + function_name, + retval, + param_count, + params + ) + + - Deprecated API. Checks that `function_name (and optionally object`) is a valid PHP + callable via `zend_is_callable_ex()` and calls it with the given parameters and return + value. `function_table is ignored and should always be NULL`. It is recommended to + instead use `zend_is_callable()` to fetch an FCC and call it with + `zend_call_known_fcc()`, see below for more details. + + - - .. code:: c + + call_user_function_named( + function_table, + object, + function_name, + retval, + param_count, + params, + named_params + ) + + - Deprecated API. Same as `call_user_function() with the extra named_params` argument. + +*********************** + zend_fcall_info_cache +*********************** + +.. code:: c + + typedef struct _zend_fcall_info_cache { + zend_function *function_handler; + zend_class_entry *calling_scope; + zend_class_entry *called_scope; + zend_object *object; /* Pointer for object representing $this */ + zend_object *closure; /* Closure reference, only if the callable *is* the object */ + } zend_fcall_info_cache; + +`function_handler`: + The actual body of a PHP function that will be used by the VM, can be retrieved from the global + function table or a class function table (`zend_class_entry->function_table). May be NULL` + for trampolines, see details below. + +`object`: + If the function is an instance object method, this field represents the value of $this. Should + be `NULL` for static methods and global functions. + +`called_scope`: + The scope in which to call the method, generally it's `object->ce`. + +`calling_scope`: + The scope in which this call is made, only used by `zend_is_callable_ex()` to resolve the + actual scope for the deprecated partially supported callables (e.g. ``[$obj, + "parent::method"]``). Can be ignored as it is not used when actually calling a function. + +`closure`: + Storage field for the `Closure object or the object containing the __invoke()` / + `__call() / __callStatic() method pointed at in function_handler`. + +.. note:: + + Prior to PHP 8.3.0, the FCC did not contain the `closure` field, and the relevant FCC API + didn't exist. + +FCCs are the goto structure to handle and store PHP callables as most of the time they don't need +any reference counting. The one exception is when the FCC represents a trampoline. A trampoline is a +heap-allocated zend_function*, the most common trampolines are calls to a non existing class +method handled by the `__call() or __callStatic()` magic methods. A trampoline releases the +`zend_function* when it is called, as the op array will be consumed. However the f` ZPP +argument specifier will *free* the trampoline and assign `NULL to function_handler`, making +the FCC uninitialized. The trampoline can be manually fetched by using `zend_is_callable()` or to +prevent ZPP from freeing it one can use the `F argument specifier. However, in that case careful +consideration need to be done to free the potential trampoline in any failure path, including ZPP +failure. + +The *only* case where an FCC may be uninitialized is if the function is a trampoline, i.e. when the +method of a class does not exist but is handled by the magic methods +`__call()/__callStatic()`. This is because a trampoline is freed by ZPP as it is a newly +allocated `zend_function` struct with the op array copied, and is freed when called. To retrieve +it manually use `zend_is_callable_ex(). To free a trampoline the `void +zend_release_fcall_info_cache(zend_fcall_info_cache *fcc)`` should be used to release it. This +function can be called even if the FCC is not a trampoline. However, if a reference to the closure +is kept, this must be called *prior* to freeing the `closure`, as the trampoline will partially +refer to a `zend_function *` entry in the closure CE. + +.. note:: + + There is investigative work being done to simplify the handling of trampoline in the future. + +FCC API +======= + +The FCC API is defined in `Zend/zend_API.h`. + +.. list-table:: `zend_fcall_info_cache` API + :header-rows: 1 + + - - Function/Macro + - Description + + - - `ZEND_FCC_INITIALIZED(fcc)` + - Check if an FCC is initialized. + + - - .. code:: c + + zend_fcc_addref( + zend_fcall_info_cache *fcc + ) + + - Adds necessary reference increments and copies to an FCC pointer. + + - - .. code:: c + + zend_fcc_dup( + zend_fcall_info_cache *dest, + const zend_fcall_info_cache *src, + ) + + - Duplicates the source FCC pointer into the destination FCC pointer. + + - - `zend_fcc_equals(fcc_1, fcc_2)` + - Determines if two FCCs represent the same function. + + - - `zend_fcc_closure_equals_ex(fcc_1, fcc_2)` + - Determine if both FCCs represent the same function as a `Closure` may be a + `ZEND_ACC_FAKE_CLOSURE` + + - - .. code:: c + + zend_fcc_dtor( + zend_fcall_info_cache *fcc + ) + + - Releases data hold by an FCC, including freeing trampolines. + + - - `zend_get_gc_buffer_add_fcc(gc_buffer, fcc)` + - For use with the `get_gc` object handler in case the FCC is stored by an internal object. + + - - .. code:: c + + zend_call_known_fcc( + zend_fcall_info_cache *fcc, + zval *retval, + uint32_t param_count, + zval *params, + HashTable *named_params + ) + + - Calls an initialized FCC, all other parameters are optional. + + - - `zend_get_callable_zval_from_fcc(fcc, callable)` + - Creates a representation of the FCC into the given callable zval*. Note static class + methods that can be represented as strings or an array pair are returned as an array pair + + - - .. code:: c + + zend_release_fcall_info_cache( + zend_fcall_info_cache *fcc + ) + + - Release potential trampoline held by the FCC. + +***************** + zend_fcall_info +***************** + +.. code:: c + + typedef struct _zend_fcall_info { + size_t size; + zval function_name; + zval *retval; + zval *params; + zend_object *object; + uint32_t param_count; + uint32_t consumed_args; + /* This hashtable can also contain positional arguments (with integer keys), + * which will be appended to the normal params[]. This makes it easier to + * integrate APIs like call_user_func_array(). The usual restriction that + * there may not be position arguments after named arguments applies. */ + HashTable *named_params; + } zend_fcall_info; + +`size`: + Mandatory field, which is the size of an FCI structure, thus always: `sizeof(zend_fcall_info)` + +`function_name`: + The actual callable, do not be fooled by the name of this field as this is a leftover when PHP + didn't have objects and class methods. It must be a string zval or an array following the same + rules as callables in PHP, namely the first index is a class or instance object, and the second + one is the method name. If an initialized FCC representing the function exists, it may be + omitted. + +`object`: + Only used for callability checks in conjunction with `function_name`, see + `zend_is_callable_ex()` for more details. If an initialized FCC representing the function + exists, it may be omitted. + +`retval`: + Mandatory field, which will contain the result of the PHP function. + +`param_count`: + Mandatory field, the number of arguments that will be provided to this call to the function. + +`consumed_args`: + Specifies positional arguments whose ownership is transfered to the function being called. This + is used as an optimization to avoid an unnecessary `Z_ADDREF_P()/zval_ptr_dtor()` when the + value is not used after the call. Only one argument can be specified at once with + `zend_fci_consumed_arg(n)` (zero-based), and the value must not be a reference. Must be set to + `0` when no parameter applies. New since PHP 8.6. + +`params`: + contains positional arguments that will be provided to this call to the function. If + `param_count = 0, it can be NULL`. + +`named_params`: + A HashTable containing named (or positional) arguments. + +FCI API +======= + +The FCI API is defined in `Zend/zend_API.h`. + +.. list-table:: `zend_fcall_info` API + :header-rows: 1 + + - - Function/Macro + - Description + + - - `ZEND_FCI_INITIALIZED(fci)` + - Check if an FCI is initialized. + + - - `zend_call_function(fci, fcc)` + + - Calls a function, `fcc is a pointer to an FCC that may be NULL`, in this case the FCC + will be derived from the FCI function_name zval, including a callability check. This + function only returns `FAILURE if EG(active)` is false during late engine shutdown. + If the call or call setup throws, EG(exception) will be set and the retval will be UNDEF. + Otherwise, the retval will be a non-UNDEF value. + + - - .. code:: c + + zend_call_function_with_return_value( + zend_fcall_info *fci, + zend_fcall_info_cache *fcc, + zval *retval + ) + + - Same as `zend_call_function but sets the FCI return value to retval`. + + - - .. code:: c + + zend_fcall_info_init( + const zval *callable, + uint32_t check_flags, + zend_fcall_info *fci, + zend_fcall_info_cache *fcc, + zend_string **callable_name, + char **error + ) + + - Build a FCI/FCC pair from a `zval* by forwarding callable, check_flags, fcc`, + `callable_name, and error to zend_is_callable_ex(). As such callable_name` + and `error are optional and may be NULL. Returns FAILURE if callable` is not + a PHP callable, `SUCCESS` otherwise. + +Deprecated FCI API +================== + +There is also a set of FCI API that is now considered deprecated as it is unintuitive and does +unnecessary parameter copying. + +In general the IS_ARRAY zval should be assigned to the `named_params` field of the FCI instead of +copying the arguments. + +.. list-table:: Deprecated `zend_fcall_info` API + :header-rows: 1 + + - - Function + - Description + + - - .. code:: c + + zend_fcall_info_call( + zend_fcall_info *fci, + zend_fcall_info_cache *fcc, + zval *retval, + zval *args + ) + + - Call a function using information created by zend_fcall_info_init()/args() functions. If + args is given then those replace the argument info in FCI is temporarily. `args` *must* + be an IS_ARRAY zval. It is recommended to replace calls to this function by + `zend_call_function_with_return_value() and if args is not NULL` to set the + `fci.named_params = Z_ARR_P(args);`. + + - - .. code:: c + + zend_fcall_info_args_clear( + zend_fcall_info *fci, + bool free_mem + ) + + - Clear arguments connected with the FCI. If free_mem is true then the params array gets + free'd as well. + + - - .. code:: c + + zend_fcall_info_args_save( + zend_fcall_info *fci, + uint32_t *param_count, + zval **params + ) + + - Save current arguments from the FCI, the params array will be set to `NULL`. + + - - .. code:: c + + zend_fcall_info_args_restore( + zend_fcall_info *fci, + uint32_t param_count, + zval *params + ) + + - Free arguments connected with the FCI, and set back saved ones. + + - - .. code:: c + + zend_fcall_info_args( + zend_fcall_info *fci, + zval *args + ) + + - Sets the arguments in the FCI taking care of refcount. `args` *must* be an IS_ARRAY zval. + If `args is NULL` the FCI arguments are cleared. It is recommended to migrate away + from this API by setting `fci.named_params=Z_ARR_P(args)` and calling + `zend_call_function(fci, NULL)`. + + - - .. code:: c + + zend_fcall_info_args_ex( + zend_fcall_info *fci, + zend_function *func, + zval *args + ) + + - Same as `zend_fcall_info_args() but if func is not NULL` it will check if a + by-value argument needs to be passed by-ref and wraps it in a reference. This behaviour is + **NON STANDARD** PHP behaviour and should never be used. + + - - .. code:: c + + zend_fcall_info_argp( + zend_fcall_info *fci, + uint32_t argc, + zval *argv + ) + + - Sets the arguments in the FCI taking care of refcount. If `argc is 0` the FCI + arguments are cleared, else pass a variable amount of zval** arguments. + + - - .. code:: c + + zend_fcall_info_argv( + zend_fcall_info *fci, + uint32_t argc, + va_list *argv + ) + + - Sets the arguments in the FCI taking care of refcount. If `argc is 0` the FCI + arguments are cleared, else pass a variable amount of zval** arguments. + + - - .. code:: c + + zend_fcall_info_argn( + zend_fcall_info *fci, + uint32_t argc, + ... + ) + + - Sets the arguments in the FCI taking care of refcount. If `argc is 0` the FCI + arguments are cleared, else pass a variable amount of zval** arguments. diff --git a/docs/source/core/data-structures/index.rst b/docs/source/core/data-structures/index.rst index 8fcb860f686b..8268010859d7 100644 --- a/docs/source/core/data-structures/index.rst +++ b/docs/source/core/data-structures/index.rst @@ -9,5 +9,6 @@ reference-counting zend_string zend_constant + fci-fcc This section provides an overview of the core data structures used in php-src.

« previous php.cvs (#142192) next »