The PHP Request Life Cycle
Table of Contents

The PHP Request Life Cycle

Now that you have a decent understanding of how the Zend Engine works, let's look at how the engine sits inside PHP and how PHP itself sits inside other applications.

Any discussion of the architecture of PHP starts with a diagram such as Figure 20.2, which shows the application layers in PHP.

Figure 20.2. The architecture of PHP.

The outermost layer, where PHP interacts with other applications, is the Server Abstraction API (SAPI) layer. The SAPI layer partially handles the startup and shutdown of PHP inside an application, and it provides hooks for handling data such as cookies and POST data in an application-agnostic manner.

Below the SAPI layer lies the PHP engine itself. The core PHP code handles setting up the running environment (populating global variables and setting default .ini options), providing interfaces such as the stream's I/O interface, parsing of data, and most importantly, providing an interface for loading extensions (both statically compiled extensions and dynamically loaded extensions).

At the core of PHP lies the Zend Engine, which we have discussed in depth here. As you've seen, the Zend Engine fully handles the parsing and execution of scripts. The Zend Engine was also designed for extensibility and allows for entirely overriding its basic functionality (compilation, execution, and error handling), overriding selective portions of its behavior (overriding op_handlers in particular ops), and having functions called on registerable hooks (on every function call, on every opcode, and so on). These features allow for easy integration of caches, profilers, debuggers, and semantics-altering extensions.

The SAPI Layer

The SAPI layer is the abstraction layer that allows for easy embedding of PHP into other applications. Some SAPIs include the following:

  • mod_php5 This is the PHP module for Apache, and it is a SAPI that embeds PHP into the Apache Web server.

  • fastcgi This is an implementation of FastCGI that provides a scalable extension to the CGI standard. FastCGI is a persistent CGI daemon that can handle multiple requests. FastCGI is the preferred method of running PHP under IIS and shows performance almost as good as that of mod_php5.

  • CLI This is the standalone interpreter for running PHP scripts from the command line, and it is a thin wrapper around a SAPI layer.

  • embed This is a general-purpose SAPI that is designed to provide a C library interface for embedding a PHP interpreter in an arbitrary application.

The idea is that regardless of the application, PHP needs to communicate with an application in a number of common places, so the SAPI interface provides a hook for each of those places. When an application needs to start up PHP, for instance, it calls the startup hook. Conversely, when PHP wants to output information, it uses the provided ub_write hook, which the SAPI layer author has coded to use the correct output method for the application PHP is running in.

To understand the capabilities of the SAPI layer, it is easiest to look at the hooks it implements. Every SAPI interface registers the following struct, with PHP describing the callbacks it implements:

struct _sapi_module_struct {
  char *name;
  char *pretty_name;
  int (*startup)(struct _sapi_module_struct *sapi_module);
  int (*shutdown)(struct _sapi_module_struct *sapi_module);
  int (*activate)(TSRMLS_D);
  int (*deactivate)(TSRMLS_D);
  int (*ub_write)(const char *str, unsigned int str_length TSRMLS_DC);
  void (*flush)(void *server_context);
  struct stat *(*get_stat)(TSRMLS_D);
  char *(*getenv)(char *name, size_t name_len TSRMLS_DC);
  void (*sapi_error)(int type, const char *error_msg, ...);
  int (*header_handler)(sapi_header_struct *sapi_header,
                        sapi_headers_struct *sapi_headers TSRMLS_DC);
  int (*send_headers)(sapi_headers_struct *sapi_headers TSRMLS_DC);
  void (*send_header)(sapi_header_struct *sapi_header,
                      void *server_context TSRMLS_DC);
  int (*read_post)(char *buffer, uint count_bytes TSRMLS_DC);
  char *(*read_cookies)(TSRMLS_D);
  void (*register_server_variables)(zval *track_vars_array TSRMLS_DC);
  void (*log_message)(char *message);
  char *php_ini_path_override;
  void (*block_interruptions)(void);
  void (*unblock_interruptions)(void);
  void (*default_post_reader)(TSRMLS_D);
  void (*treat_data)(int arg, char *str, zval *destArray TSRMLS_DC);
  char *executable_location;
  int php_ini_ignore;
  int (*get_fd)(int *fd TSRMLS_DC);
  int (*force_http_10)(TSRMLS_D);
  int (*get_target_uid)(uid_t * TSRMLS_DC);
  int (*get_target_gid)(gid_t * TSRMLS_DC);
  unsigned int (*input_filter)(int arg, char *var,
                               char **val, unsigned int val_len TSRMLS_DC);
  void (*ini_defaults)(HashTable *configuration_hash);
  int phpinfo_as_text;

The following are some of the notable elements from this example:

  • startup This is called the first time the SAPI is initialized. In an application that will serve multiple requests, this is performed only once. For example, in mod_php5, this is performed in the parent process before children are forked.

  • activate This is called at the beginning of each request. It reinitializes all the per-request SAPI data structures.

  • deactivate This is called at the end of each request. It ensures that all data has been correctly flushed to the application, and then it destroys all the per-request data structures.

  • shutdown This is called at interpreter shutdown. It destroys all the SAPI structures.

  • ub_write This is what PHP will use to output data to the client. In the CLI SAPI, this is as simple as writing to standard output; in mod_php5, the Apache library call rwrite is called.

  • sapi_error This is a handler for reporting errors to the application. Most SAPIs use php_error, which instructs PHP to use its own internal error system.

  • flush This tells the application to flush its output. In the CLI, this is implemented via the C library call fflush; mod_php5 uses the Apache library rflush.

  • send_header This sends a single specified header to the client. Some servers (such as Apache) have built-in functions for handling header transmission. Others (such as the PHP CGI) require you to manually send them. Others still (such as the CLI) do not handle sending headers at all.

  • send_headers This sends all headers to the client.

  • read_cookies During SAPI activation, if a read_cookies handler is defined, it will be called to populate SG(request_info).cookie_data. This is then used to populate the $_COOKIE autoglobal.

  • read_post During SAPI activation, if the request method is a POST (or if the php.ini variable always_populate_raw_post_data is true), the read_post handler is called to populate $HTTP_RAW_POST_DATA and $_POST.

Chapter 23 takes a closer look at using the SAPI interface to integrate PHP into applications and does a complete walkthrough of the CGI SAPI.

The PHP Core

There are several key steps in activating and running a PHP interpreter. When an application wants to start a PHP interpreter, it starts by calling php_module_startup. This function is like the master switch that turns on the interpreter. It activates the registered SAPI, initializes the output buffering system, starts the Zend Engine, reads in and acts on the php.ini file, and prepares the interpreter for its first request. Some important functions that are used in the core are

  • php_module_startup This is the master startup for PHP.

  • php_startup_extensions This runs the initialization function in all registered extensions.

  • php_output_startup This starts the output system.

  • php_request_startup At the beginning of a request, this is the master function, which calls up to the SAPI per-request functions, calls down into the Zend Engine for per-request initialization, and calls the request startup function in all registered modules.

  • php_output_activate This activates the output system, setting the output functions to use the SAPI-specified output functions.

  • php_init_config This reads in the php.ini file and acts on its contents.

  • php_request_shutdown This is the master function to destroy per-request resources.

  • php_end_ob_buffers This is used to flush output buffers, if output buffering has been enabled.

  • php_module_shutdown This is the master shutdown function for PHP, triggering all the rest of the interpreter shutdown functions.

The PHP Extension API

Most of our discussion regarding the PHP extension API will be carried on in Chapter 22, where you will actually implement extensions. Here we'll only look at the basic callbacks available to extensions and when they are called.

Extensions can be registered in two ways. When an extension is compiled statically into PHP, the configuration system permanently registers that module with PHP. An extension can also be loaded from the .ini file, in which case it is registered during the .ini parsing.

The hooks that an extension can register are contained in its zend_module_entry function, like so:

struct _zend_module_entry {
  unsigned short size;
  unsigned int zend_api;
  unsigned char zend_debug;
  unsigned char zts;
  struct _zend_ini_entry *ini_entry;
  char *name;
  zend_function_entry *functions;
  int (*module_startup_func)(INIT_FUNC_ARGS);
  int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
  int (*request_startup_func)(INIT_FUNC_ARGS);
  int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
  void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
  char *version;
  int (*global_startup_func)(void);
  int (*global_shutdown_func)(void);
  int globals_id;
  int module_started;
  unsigned char type;
  void *handle;
  int module_number;

The following are some important elements of this struct:

  • module_startup_func This hook is called when the module is first loaded. This traditionally registers globals, performs any one-time initializations, and registers any .ini file entries that the module wants to use. In some pre-fork architectures, notably Apache, this function is called in the parent process, before forking. This makes it an inappropriate place to initialize open sockets or database connections because they may not behave well if multiple processes try to use the same resources.

  • module_shutdown_func This hook is called when the interpreter shuts down. Any resources that the module has allocated should be freed here.

  • request_startup_func This is called at the beginning of each request. This hook is particularly useful for setting up any sort of per-request resources that a script may need.

  • request_shutdown_func This is called at the end of every request.

  • functions This is the function that the extension defines.

  • ini_functions This is the .ini file entries that the extension registers.

The Zend Extension API

The final component of the PHP request life cycle is the extension API that the Zend Engine itself provides for extensibility. There are two major components of the extensibility: Certain key internal functions are accessed via function pointers, meaning that they can be overridden at runtime, and there is a hook API that allows an extension to register code to be run before certain opcodes.

These are the main function pointers used in the Zend Engine:

  • zend_compile We discussed this function at the beginning of the chapter. zend_compile is the wrapper for the lexer, parser, and code generator. APC and the other compiler caches overload this pointer so that they can return cached copies of scripts' op arrays.

  • zend_execute Also discussed earlier in this chapter, this is the function that executes the code generated by zend_compile. APD and the other code profilers overload zend_execute so that they can track with high granularity the time spent in every function call.

  • zend_error_cb This is a pointer that sets the function called anytime an error is triggered in PHP. If you wanted to write an extension that automatically converts errors to exceptions, this would be the place to do it.

  • zend_fopen This is the function that implements the open call that is used internally whenever a file needs to be opened.

The hook API is an extension of the PHP extension API:

struct _zend_extension {
  char *name;
  char *version;
  char *author;
  char *URL;
  char *copyright;

  startup_func_t startup;
  shutdown_func_t shutdown;
  activate_func_t activate;
  deactivate_func_t deactivate;
  message_handler_func_t message_handler;
  op_array_handler_func_t op_array_handler;
  statement_handler_func_t statement_handler;
  fcall_begin_handler_func_t fcall_begin_handler;
  fcall_end_handler_func_t fcall_end_handler;
  op_array_ctor_func_t op_array_ctor;
  op_array_dtor_func_t op_array_dtor;
  int (*api_no_check)(int api_no);
  void *reserved2;
  void *reserved3;
  void *reserved4;
  void *reserved5;
  void *reserved6;
  void *reserved7;
  void *reserved8;
  DL_HANDLE handle;
  int resource_number;

The pointers provide the following functionality:

  • startup This is functionally identical to an extension's module_startup_func function.

  • shutdown This is functionally identical to an extension's module_shutdown_func function.

  • activate This is functionally identical to an extension's request_startup_func function.

  • deactivate This is functionally identical to an extension's request_shutdown_func function.

  • message_handler This is called when the extension is registered.

  • op_array_handler This is called on a function's op_array after the function is compiled.

  • statement_handler If this handler is set, an additional opcode is inserted before every statement. This opcode's handler executes all the registered statement handlers. This handler can be useful for debugging extensions, but because it effectively doubles the size of the script's op array, it can have a deleterious effect on system performance.

  • fcall_begin_handler If this handler is set, an additional opcode is inserted before every ZEND_DO_FCALL and ZEND_DO_FCALL_BY_NAME opcode. That opcode's handler executes all registered fcall_begin_handler functions.

  • fcall_end_handler If this handler is set, an additional opcode is inserted after every ZEND_DO_FCALL and ZEND_DO_FCALL_BY_NAME opcode. That opcode's handler executes all registered fcall_end_handler functions.

How All the Pieces Fit Together

The preceding sections provide a lot of information. PHP, SAPIs, the Zend Enginethere are a lot of moving parts to consider. The most important part in understanding how a system works is understanding how all the pieces fit together. Each SAPI is unique in how it ties all the pieces together, but all the SAPIs follow the same basic pattern.

Figure 20.3 shows the complete life cycle of the mod_php5 SAPI. After the initial server startup, the process loops the handling requests.

Figure 20.3. The mod_php5 request life cycle.

Table of Contents