Приглашаем посетить
Чернышевский (chernyshevskiy.lit-info.ru)

Functions

Previous
Table of Contents
Next

Functions

You've seen that when a piece of code calls a function, it populates the argument stack via ZEND_SEND_VAL and uses a ZEND_DO_FCALL op to execute the function. But what does that really do? To really understand how these things work, you need to go back to even before compilation. When PHP starts up, it looks through all its registered extensions (both the ones that were compiled statically and any that were registered in the php.ini file) and registers all the functions that they define. These functions look like this:

typedef struct _zend_internal_function {
  /* Common elements */
  zend_uchar type;
  zend_uchar *arg_types;
  char *function_name;
  zend_class_entry *scope;
  zend_uint fn_flags;
  union _zend_function *prototype;
  /* END of common elements */
  void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
} zend_internal_function;

The important things to note here are the type (which is always ZEND_INTERNAL_FUNCTION, meaning that it is an extension function written in C), the function name, and the handler, which is a C function pointer to the function itself and is part of the extension code.

Registering one of these functions basically amounts to its being inserted into the global function table (a hashtable in which functions are stored).

User-defined functions are, of course, inserted by the compiler. When the compiler (by which I still mean the lexer, parser, and code generator all together) encounters a piece of code like this:

function say_hello($name)
{
  echo "Hello $name\n";
}

it compiles the code inside the function's block as a new op array, creates a zend_function with that op array, and inserts that zend_function into the global function table with its type set to ZEND_USER_FUNCTION. A zend_function looks like this:

typedef union _zend_function {
  zend_uchar type;
  struct {
    zend_uchar type;  /* never used */
    zend_uchar *arg_types;
    char *function_name;
    zend_class_entry *scope;
    zend_uint fn_flags;
    union _zend_function *prototype;
  } common;
  zend_op_array op_array;
  zend_internal_function internal_function;
} zend_function;

This definition can be rather confusing if you don't recognize one of the design goals: For the most part, zend_functions are zend_internal_functions are op arrays. They are not identical structs, but all the elements that are in "common" they hold in common. Thus they can safely be casted to each other.

In practice, this means that when a ZEND_DO_FCALL op is executed, it stashes away the current scope, populates the argument stack, and looks up the requested function by name (actually by the lowercase version of the name because PHP implements case-insensitive function names), returning a pointer to a zend_function. If the function's type is ZEND_INTERNAL_FUNCTION, it can be recast to a zend_internal_function and executed via zend_execute_internal, which executes internal functions. Otherwise, it will be executed via zend_execute, the same function that is called to execute scripts and includes. This works because for user functions are completely identical to op arrays.

As you can likely infer from the way that PHP functions work, ZEND_SEND_VAL does not push an argument's zval onto the argument stack; instead, it copies it and pushes the copy onto the stack. This has the consequence that unless a variable is passed by reference (with the exception of objects), changing its value in a function does not change the argument passedit changes only the copy. To change a passed argument in a function, pass it by reference.


Previous
Table of Contents
Next