Press "Enter" to skip to content

PHP Internals — Functions (Introspecting PHP Function)

In PHP, functions come in two kinds:

  • One is zend_internal_function — supplied by an extension or by the Zend/PHP kernel itself, written in 'C/C++', and directly executable.
  • The other is zend_user_function — the kind we see all the time, a function defined by the user in a PHP script; such a function is eventually translated by the ZE into an opcode array for execution
  • Looking at zend_compile.h, we can find the following 3 structures:

    typedef struct _zend_internal_function {
        /* Common elements */
        zend_uchar type;
        char * function_name;
        zend_class_entry *scope;
        zend_uint fn_flags;
        union _zend_function *prototype;
        zend_uint num_args;
        zend_uint required_num_args;
        zend_arg_info *arg_info;
        zend_bool pass_rest_by_reference;
        unsigned char return_reference;
        /* END of common elements */
        void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
        struct _zend_module_entry *module;
    } zend_internal_function;
    struct _zend_op_array {
        /* Common elements */
        zend_uchar type;
        char *function_name;
        zend_class_entry *scope;
        zend_uint fn_flags;
        union _zend_function *prototype;
        zend_uint num_args;
        zend_uint required_num_args;
        zend_arg_info *arg_info;
        zend_bool pass_rest_by_reference;
        unsigned char return_reference;
        /* END of common elements */
        zend_uint *refcount;
        zend_op *opcodes;
        zend_uint last, size;
        zend_compiled_variable *vars;
        int last_var, size_var;
        zend_uint T;
        zend_brk_cont_element *brk_cont_array;
        zend_uint last_brk_cont;
        zend_uint current_brk_cont;
        zend_try_catch_element *try_catch_array;
        int last_try_catch;
        /* static variables support */
        HashTable *static_variables;
        zend_op *start_op;
        int backpatch_count;
        zend_bool done_pass_two;
        zend_bool uses_this;
        char *filename;
         zend_uint line_start;
        zend_uint line_end;
        char *doc_comment;
        zend_uint doc_comment_len;
        void *reserved[ZEND_MAX_RESERVED_RESOURCES];
    };
    typedef union _zend_function {
        zend_uchar type;    /* MUST be the first element of this struct! */
        struct {
            zend_uchar type;  /* never used */
            char *function_name;
            zend_class_entry *scope;
            zend_uint fn_flags;
            union _zend_function *prototype;
            zend_uint num_args;
            zend_uint required_num_args;
            zend_arg_info *arg_info;
            zend_bool pass_rest_by_reference;
            unsigned char return_reference;
        } common;
        zend_op_array op_array;
        zend_internal_function internal_function;
    } zend_function;
        

    The first structure defines zend_internal_function. When PHP starts up, it walks every loaded extension module, and for each function named in the module's function_entry it creates a zend_internal_function structure, sets type to ZEND_INTERNAL_FUNCTION (see the table below), and fills that structure into the global function table (a HashTable);

    #define ZEND_INTERNAL_FUNCTION              1
    #define ZEND_USER_FUNCTION                  2
    #define ZEND_OVERLOADED_FUNCTION            3
    #define ZEND_EVAL_CODE                      4
    #define ZEND_OVERLOADED_FUNCTION_TEMPORARY  5
          

    The second structure, op_array, is a very important one, because:

          extern ZEND_API zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type TSRMLS_DC);
       

    That is to say, the PHP scripts we write are all translated by the ZE into an op_array, and are finally handed to zend_execute for execution.

    What's more, in the ZE a user-defined function (userland function) is likewise translated into an op_array and filled into the global function table. In that case both scope and function_name are non-null. As for code that runs directly in the global scope, the resulting op_array has scope set to global and function_name empty.

    The third structure is quite interesting. To understand it you first have to understand its design goal: zend_internal_function, zend_function and zend_op_array can be safely cast to one another (They are not identical structs, but all the elements that are in "common" they hold in common, thus the can safely be casted to each other);

    Concretely, when a function is called through ZEND_DO_FCALL in the op code, the ZE looks the function up in the function table by name (actually by the lowercased function name, which is exactly why PHP function names are case-insensitive). If it is found, a pointer to a zend_function structure is returned (look carefully at the zend_function structure above), and then the type is checked: if it is ZEND_INTERNAL_FUNCTION, the ZE calls zend_execute_internal and executes the function through zend_internal_function.handler; if it is not, it calls zend_execute to execute the zend_op_array contained in that function.

    Be First to Comment

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    This site uses Akismet to reduce spam. Learn how your comment data is processed.