|
| 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