Skip to content

Commit 07024bd

Browse files
Girgiascmb69
andauthored
Add explanation about FCI/FCC and how to use (#135)
Co-authored-by: Christoph M. Becker <[email protected]>
1 parent abb5817 commit 07024bd

File tree

2 files changed

+153
-2
lines changed

2 files changed

+153
-2
lines changed
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
Functions: zend_function
1+
Functions
22
========================
33

4-
Here we detail how functions work in PHP.
4+
The body of PHP functions are represented with the ``zend_function`` structure.
5+
However, handling them is rarely done as they are solely needed for the VM.
6+
In general PHP ``callable`` s are what will need to be dealt with, which are represented by the pair of
7+
``zend_fcall_info``/``zend_fcall_info_cache`` structures.
8+
9+
10+
TODO: Detail ``zend_function``
11+
12+
.. toctree::
13+
:maxdepth: 2
14+
15+
functions/callables.rst
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
PHP Callables
2+
===================
3+
4+
Dealing with PHP functions in C requires the knowledge of the following two structures
5+
``zend_fcall_info``/``zend_fcall_info_cache``. The first one necessarily contains the information for calling
6+
the function, such as arguments and the return value, but may also include the actual callable.
7+
The latter *only* contains the callable. We will use the commonly used abbreviation of FCI and FCC when talking about
8+
``zend_fcall_info`` and ``zend_fcall_info_cache`` respectively.
9+
You will most likely encounter those when using the ZPP ``f`` argument flag, or when you need to call a PHP function
10+
or method from within an extension.
11+
12+
Structure of ``zend_fcall_info``
13+
--------------------------------
14+
15+
.. warning:: The implementation of ``zend_fcall_info`` is widely different prior to PHP 7.1.0.
16+
17+
As of PHP 8.0.0, ``zend_fcall_info`` has the following structure:
18+
19+
::
20+
21+
struct _zend_fcall_info {
22+
size_t size;
23+
zval function_name;
24+
zval *retval;
25+
zval *params;
26+
zend_object *object;
27+
uint32_t param_count;
28+
/* This hashtable can also contain positional arguments (with integer keys),
29+
* which will be appended to the normal params[]. This makes it easier to
30+
* integrate APIs like call_user_func_array(). The usual restriction that
31+
* there may not be position arguments after named arguments applies. */
32+
HashTable *named_params;
33+
} zend_fcall_info;
34+
35+
36+
Let detail the various FCI fields:
37+
38+
``size``:
39+
Mandatory field, which is the size of an FCI structure, thus always: ``sizeof(zend_fcall_info)``
40+
``function_name``:
41+
Mandatory field, the actual callable, do not be fooled by the name of this field as this is a leftover when
42+
PHP didn't have objects and class methods. It must be a string zval or an array following the same rules as
43+
callables in PHP, namely the first index is a class or instance object, and the second one is the method name.
44+
It can also be undefined if, and only if, a non empty FCC is provided.
45+
``retval``:
46+
Mandatory field, which will contain the result of the PHP function
47+
``param_count``:
48+
Mandatory field, the number of arguments that will be provided to this call to the function
49+
``params``:
50+
contains positional arguments that will be provided to this call to the function.
51+
If ``param_count = 0``, it can be ``NULL``.
52+
``object``:
53+
The object on which to call the method name stored in ``function_name``, or ``NULL`` if no objects are involved.
54+
``named_params``:
55+
A HashTable containing named (or positional) arguments.
56+
57+
.. note:: Prior to PHP 8.0.0, the ``named_params`` field did not exist. However, a ``zend_bool no_separation;``
58+
field existed which specified if array arguments should be separated or not.
59+
60+
Structure of ``zend_fcall_info_cache``
61+
--------------------------------------
62+
63+
A ``zend_fcall_info_cache`` has the following structure:
64+
65+
::
66+
67+
typedef struct _zend_fcall_info_cache {
68+
zend_function *function_handler;
69+
zend_class_entry *calling_scope;
70+
zend_class_entry *called_scope;
71+
zend_object *object;
72+
} zend_fcall_info_cache;
73+
74+
Let detail the various FCC fields:
75+
76+
``function_handler``:
77+
The actual body of a PHP function that will be used by the VM, can be retrieved from the global function table
78+
or a class function table (``zend_class_entry->function_table``).
79+
``object``:
80+
If the function is an object method, this field is the relevant object.
81+
``called_scope``:
82+
The scope in which to call the method, generally it's ``object->ce``.
83+
``calling_scope``:
84+
The scope in which this call is made, only used by the VM.
85+
86+
.. note:: To release a FCC you should use the ``void zend_release_fcall_info_cache(zend_fcall_info_cache *fcc)``
87+
function.
88+
89+
.. warning:: Prior to PHP 7.3.0 there existed an ``initialized`` field. Now an FCC is considered initialized when
90+
``function_handler`` is set to a non-null pointer.
91+
92+
Zend Engine API for callables
93+
-----------------------------
94+
95+
The API can be found in the ``Zend_API.h`` header file.
96+
97+
If you have a FCI/FCC pair for a callable you can call it directly by using the
98+
``zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache)`` function.
99+
If you just need to change, or provide the arguments and return value you can use the
100+
``zend_fcall_info_call(zend_fcall_info *fci, zend_fcall_info_cache *fcc, zval *retval, zval *args)`` function.
101+
102+
In the more likely case where you just have a callable zval, you have the choice of a couple different options
103+
depending on the use case.
104+
105+
For a one-off call the ``call_user_function(function_table, object, function_name, retval_ptr, param_count, params)``
106+
and ``call_user_function_named(function_table, object, function_name, retval_ptr, param_count, params, named_params)``
107+
macro-functions will do the trick.
108+
109+
.. note:: As of PHP 7.1.0, the ``function_table`` argument is not used and should always be ``NULL``.
110+
111+
The drawback of those functions is that they will verify the zval is indeed callable, and create a FCI/FCC pair on
112+
every call. If you know you will need to call these functions multiple time it's best to create a FCI/FCC pair yourself
113+
by using the ``zend_result zend_fcall_info_init(zval *callable, uint32_t check_flags, zend_fcall_info *fci,
114+
zend_fcall_info_cache *fcc, zend_string **callable_name, char **error)`` function.
115+
If this function returns ``FAILURE``, then the zval is not a proper callable.
116+
``check_flags`` is forwarded to ``zend_is_callable_ex()``, generally you don't want to pass any modifying flags,
117+
however ``IS_CALLABLE_SUPPRESS_DEPRECATIONS`` might be useful in certain cases.
118+
119+
In case you just have an FCC (or a combination of ``zend_function`` and ``zend_object``) you can use the following
120+
functions::
121+
122+
/* Call the provided zend_function with the given params.
123+
* If retval_ptr is NULL, the return value is discarded.
124+
* If object is NULL, this must be a free function or static call.
125+
* called_scope must be provided for instance and static method calls. */
126+
ZEND_API void zend_call_known_function(
127+
zend_function *fn, zend_object *object, zend_class_entry *called_scope, zval *retval_ptr,
128+
uint32_t param_count, zval *params, HashTable *named_params);
129+
130+
/* Call the provided zend_function instance method on an object. */
131+
static zend_always_inline void zend_call_known_instance_method(
132+
zend_function *fn, zend_object *object, zval *retval_ptr,
133+
uint32_t param_count, zval *params)
134+
{
135+
zend_call_known_function(fn, object, object->ce, retval_ptr, param_count, params, NULL);
136+
}
137+
138+
And specific parameter number variations for the latter.
139+
140+
.. note:: If you want to call a method on an object if it exists use the ``zend_call_method_if_exists()`` function.

0 commit comments

Comments
 (0)