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

Extension Basics

Previous
Table of Contents
Next

Extension Basics

If you know C, writing PHP extensions is not terribly difficult. PHP provides a number of tools that make bridging PHP and C code easy. This section provides all the steps necessary to build a PHP extension that registers procedural functions.

Creating an Extension Stub

The easiest way to create a new extension is to use a default extension skeleton. You do this with the ext_skel script in the ext directory of the PHP source tree. To create an extension named example, you would use the following from the root of the source tree:

> cd ext
> ./ext_skel --extname=example
Creating directory example
Creating basic files: config.m4 .cvsignore example.c
php_example.h CREDITS EXPERIMENTAL tests/001.phpt example.php
[done].

To use your new extension, you have to execute the following:

1.  $ cd ..
2.  $ vi ext/example/config.m4
3.  $ ./buildconf
4.  $ ./configure --[with|enable]-example
5.  $ make
6.  $ ./php -f ext/example/example.php
7.  $ vi ext/example/example.c
8.  $ make

Repeat steps 3-6 until you are satisfied with ext/example/config.m4 and step 6 confirms that your module is compiled into PHP. Then, start writing code and repeat the last two steps as often as necessary.

This code creates a directory named example with all the files necessary to build the extension. The first file of importance is example.c; it is the master C source file for the extension. It looks like the following (from which I've trimmed some nonessential parts for readability):

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_example.h"

#define VERSION "1.0"

function_entry example_functions[] = {
 {NULL, NULL, NULL}
};

zend_module_entry example_module_entry = {
  STANDARD_MODULE_HEADER,
  "example",
  example_functions,
  PHP_MINIT(example),
  PHP_MSHUTDOWN(example),
  PHP_RINIT(example),
  PHP_RSHUTDOWN(example),
  PHP_MINFO(example),
  VERSION,
  STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_EXAMPLE
ZEND_GET_MODULE(example)
#endif

PHP_MINIT_FUNCTION(example)
{
  return SUCCESS;

PHP_MSHUTDOWN_FUNCTION(example)
{
  return SUCCESS;
}

PHP_RINIT_FUNCTION(example)
{
  return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION(example)
{
  return SUCCESS;
}

PHP_MINFO_FUNCTION(example)
{
  php_info_print_table_start();
  php_info_print_table_header(2, "example support", "enabled");
  php_info_print_table_end();
}

Later sections of this chapter discuss the meanings of the parts of this code.

The next file to inspect is the config.m4 file. This is a set of m4 macros that specify the build-time flags for the extension. The following is a simple .m4 script that requires you to specify --enable-example to build the extension:

PHP_ARG_ENABLE(example, to enable the example extension,
[  --enable-example     enable the example extension.])

if test "$PHP_EXAMPLE" != "no"; then
  PHP_NEW_EXTENSION(example, example.c, $ext_shared)
fi

The PHP build system supports the full .m4 syntax set, as well as some custom macros. Here is a partial list of the custom PHP build system macros:

  • PHP_CHECK_LIBRARY(library, func [, found [, not-found [, extra-libs]]]) Checks for the existence of the function func in the library. If the function exists, this macro evaluates to found; otherwise, it evaluates to not-found. extra_libs specifies additional libraries to add to the lib line.

  • PHP_DEFINE(what, [value]) Acts as a basic wrapper around AC_DEFUN and sets the necessary code to add the following:

    #define what value
    

  • PHP_ADD_SOURCES(path, sources[, special-flags[, type]]) Adds additional sources from path to the build. If you split extension sources across multiple files, this macro allows you to automatically build and link them all.

  • PHP_ADD_LIBRARY(library[, append[, shared-libadd]]) Adds library to the link line.

  • PHP_ADD_INCLUDE(path [,before]) Adds path to the build line. If before is set, prepend it to the include path. Otherwise, append it to the include path.

The full set of custom .m4 macros is in the file acinclude.m4 in the top level of the PHP source tree.

These are the other files created by ext_skel:

  • CREDITS This file is not necessary but is nice if you distribute an extension.

  • EXPERIMENTAL This flag file marks the extension as experimental. This is useful only if the extension is bundled with PHP itself.

  • example.php This is a sample script that loads and uses the extension.

  • php_example.h This is a default header file for the extension.

  • tests/001.phpt This is a unit test that uses the PHP build system unit-testing suite. Testing is good.

Building and Enabling Extensions

After an extension is authored, there are two ways to build it: statically or dynamically. A static extension is built into PHP when PHP itself is compiled, and a dynamic extension can be built at any time and is specified to be loaded in the php.ini file.

To build a static extension, the sources must be in a directory under ext/ in the PHP build tree. Then, from the root of the tree, you run this:

>./buildconf

This reconfigures the PHP build system and adds the configuration options to the main configuration script.

Then you can configure and build PHP as normal, enabling the extension:

> ./configure --with-apxs=/usr/local/apache/bin/apxs --enable-example
> make
> make install

To build an extension as a dynamically loadable shared object, the sources can be compiled outside the PHP source tree. From the source directory, you run this:

> phpize

This runs the PHP build system on the config.m4 file and creates a configuration script from it.

Then you configure and build the extension:

> ./configure --enable-example
> make
> make install

This builds and installs the extension in the shared extensions directory. Because it is a dynamic extension, it should also be enabled via the php.ini file, using the following:

extension=example.so

If you do not load the extension from the php.ini file, you need to load it at script execution time with the following code:

dl("example.so");

Modules loaded at execution time are unloaded at the end of the request. This is slow, so it should be done only when loading via the php.ini file is impossible for political or policy reasons. If you are uncertain whether an extension will be loaded from the php.ini file, the standard approach is to use the following block of code to detect whether the desired extension is already loaded and dynamically load the extension if it is not:

if(!extension_loaded('example')) {
        dl('example.' . PHP_SHLIB_SUFFIX);
}

Using Functions

One of the common tasks in an extension is writing functions. Whether refactoring existing PHP code in C or wrapping a C library for use in PHP, you will be writing functions.

A Function Example

To introduce function writing, let's go back to my old favorite, the Fibonacci Sequence function. First, you need a C function that can calculate Fibonacci numbers. Chapter 11, "Computational Reuse," surveys a number of Fibonacci implementations. The tail recursive version is quite fast. Here is a direct port of the PHP auxiliary tail recursion function to C:

int fib_aux(int n, int next, int result)
{
  if(n == 0) {
    return result;
  }
  return fib_aux(n - 1, next + result, next);
}

After writing the core logic of the functions, you need to write the code that actually defines a PHP function. This happens in two parts. In the first part you define the function, and in the second you register the function with the extension so that it is registered in the global function table when the extension is loaded. Here is the declaration of the function fibonacci():

PHP_FUNCTION(fibonacci)
{
  long n;
  long retval;
  if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "1", &n) == FAILURE) {
    return;
  }
  if(n < 0) {
    zend_error(E_WARNING, "Argument must be a positive integer");
    RETURN_FALSE;
  }
  retval = fib_aux(n, 1, 0);
  RETURN_LONG(retval);
}

PHP functions are declared with the PHP_FUNCTION() macro. This macro does some critical name-munging on the function name (to avoid naming conflicts between extensions) and sets up the prototype of the function. (Internally, all functions are prototyped identically.) The only thing you need to know about how this macro works is that one of the parameters passed to the function is this:

zval *return_value

This variable holds the return value from the function. There are macros that you can assign to it in common cases, but occasionally you need to assign to it directly; however, the details of direct assignment are unimportant. If you stick with the macros (as you should, and as all the bundled extensions do), you do not need to probe further into the inner workings of this macro.

PHP functions are not passed arguments directly, but instead have to extract them from an argument stack that is set by the functions' calling scope. zend_parse_parameters() extracts the variables passed into a function from PHP. The first argument passed to it, ZEND_NUM_ARGS() TSRMLS_CC, is actually two arguments. The first argument is a macro that determines the number of arguments passed on the stack, and the second, TSRMLS_CC, is a macro that passes the correct thread-safety-management data if PHP is compiled for thread safety.

The next argument passed, "l", specifies the type of data that is expectedin this case a long integer. The next argument, &n, is a reference to the C variable that you want to fill out with the value of the argument. Because you expect a long, you pass in a reference to a long.

zend_parse_parameters() returns SUCCESS if the number of arguments passed into the function matches the number of arguments searched for and if all the arguments can be successfully coerced into the types specified; otherwise, it returns FAILURE. On failure, it automatically sets the necessary warnings about the incorrect arguments passed to it, so you can simply return.

You should remember from Chapter 20, "PHP and Zend Engine Internals," that PHP variables are not C types, but instead the special zval type. zend_parse_parameters() tries to handle all the hard work of type conversion for you. For variables that map easily to primitive C types (integers, floats, and character strings), this method works well and saves a lot of hassle. For more complex types, handling the actual zval is necessary.

After the arguments have been pulled into scope, the function is really just a C function. In fibonacci(), the nth Fibonacci value is calculated and set in retval. To return this value to the PHP user, you need to set it into return_value. For simple types there are macros to handle all this. In this case, RETURN_LONG(retval); correctly sets the type of return_value, sets its internal value holder to retval, and returns from the function.

To make this function available when you load the sample extension, you need to add it the function_entry array, like this:

function_entry example_functions[] = {
  PHP_FE(fibonacci, NULL)
  {NULL, NULL, NULL}
};

The NULL after the PHP_FE() enTRy specifies argument-passing semantics (whether arguments are to be passed by reference, for example). In this case, the default passing by value is used.

If a function list appears before the functions are declared, you need to make a forward declaration of the function. This is commonly done in the header file php_example.h, as shown here:

PHP_FUNCTION(fibonacci);

Managing Types and Memory

Chapter 18, "Profiling," provides a cautionary tale about the real-life performance implications of hex-encoding strings in PHP. The hexencode() and hexdecode() functions described in that chapter were designed to take a character string and represent it as a hexadecimal string (for 8-bit safe data transfer) and use a function to reverse the process. In Chapter 18, I suggest that an alternative solution to using a workaround function would be to implement the encoding and decoding functions in C. This makes a nice function example.

You need a pair of C functions to perform this encoding. Each must take a char * string and its associated length and allocate and return its encoding or decoding. You pass the length into your functions instead of relying on a function such as strlen() so that your functions can be binary safe. In PHP, a string can actually contain arbitrary information, including null characters, so you need to pass in a string's length so that you know where the string ends.

The function hexencode() works by first allocating a buffer twice the size of its input string (because a single character is represented by a two-position hex number). The source buffer is then stepped through character by character, and the first hexadecimal value for the upper 4 bits of the char is written, followed by the value for the lower 4 bits. The string is null-terminated and returned. Here is its implementation:

const char *hexchars = "0123456789ABCDEF";
char *hexencode(char *in, int in_length) {
  char *result;
  int i;

  result = (char *) emalloc(2 * in_length + 1);
  for(i = 0; i < in_length; i++) {
    result[2*i] = hexchars[(in[i] & 0x000000f0)  >> 4];
    result[2*i + 1] = hexchars[in[i] & 0x0000000f];
  }
  result[2*in_length] = '\0';
  return result;
}

Note that the result buffer is allocated using the emalloc() function. PHP and the Zend Engine use their own internal memory-management wrapper functions. Because any data that you eventually assign into a PHP variable will be cleaned up by the Zend Engine memory-management system, that memory must be allocated with the wrapper functions. Further, because using multiple memory managers is confusing and error prone, it is a best practice to always use the Zend Engine memory-management wrappers in PHP extensions.

Table 21.1 shows the memory-management functions you will commonly need.

Table 21.1. Memory Management Functions

Function

Usage

void *emalloc(size_t size)

malloc() replacement

void efree(void *ptr)

free() replacement

void *erealloc(void *ptr, size_t size)

realloc() replacement

char *estrndup(char *str)

strndup replacement


All these functions utilize the engine's memory system which destroys all of its memory pools at the end of every request. This is fine for almost all variables because PHP is extremely well sand-boxed and its symbol tables are all destroyed between requests anyway.

Occasionally, you might need to allocate memory that is persistent between requests. A typical reason to do this would be to allocate memory for a persistent resource. To do this, there are counterparts to all the preceding functions:

void *pemalloc(size_t size, int persistent)
void pefree(void *ptr, int persistent)
void *perealloc(void *ptr, size_t size, int persistent)
char *pestrndup(char *str, int persistent)

In all cases, persistent must be set to a nonzero value for the memory to be allocated as persistent memory. Internally, setting persistent instructs PHP to use malloc() to allocate memory instead of allocating from the PHP memory-management system.

You also need a hexdecode() function. This simply reverses the process in hexencode(): The encoded string is read in two characters at a time, and the characters are converted into their corresponding ASCII equivalents. Here is the code to perform hexdecode():

static_ _inline_ _ int char2hex(char a)
{
  return (a >= 'A' && a <= 'F')?(a - 'A' + 10):( a - '0');
}
char *hexdecode(char *in, int in_length)
{
  char *result;
  int i;

  result = (char *) emalloc(in_length/2 + 1);
  for(i = 0; i < in_length/2; i++) {
    result[i] = char2hex(in[2 * i]) * 16 + char2hex(in[2 * i+1]);
  }
  result[in_length/2] = '\0';
  return result;
}

Of course, as with the Fibonacci Sequence example, these C functions are the workhorse routines. You also need PHP_FUNCTION wrappers, such as the following, for them:

PHP_FUNCTION(hexencode)
{
  char *in;
  char *out;
  int in_length;

  if(zend_parse_paramenters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &in, &in_length)
     == FAILURE) {
    return;
  }
  out = hexencode(in, in_length);
  RETURN_STRINGL(out, in_length * 2, 0);
}

PHP_FUNCTION(hexdecode)
{
  char *in;
  char *out;
  int in_length;

  if(zend_parse_paramenters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &in, &in_length)
     == FAILURE) {
    return;
  }
  out = hexdecode(in, in_length);
  RETURN_STRINGL(out, in_length/2, 0);
}

There are a couple important details to note in these code examples:

  • PHP_FUNCTION(hexencode) calls hexencode(). This is not a naming conflict because the PHP_FUNCTION() macro performs name munging.

  • zend_parse_parameters() is set up to expect a string (the format section is "s"). Because string types in PHP are binary safe, when it accepts a string, it converts it into a char * (where the actual contents are allocated) as well as an int (which stores the length of the string).

  • return_value is set via the macro RETURN_STRINGL(). This macro takes three parameters. The first is the start of a char * buffer, which holds the string, the second is the length of the string (binary safeness again), and the third is a flag to indicate whether the buffer should be duplicated for use in return_value. Because you allocated out personally, you do not need to duplicate it here (in fact, you would leak memory if you did). In contrast, if you are using a character buffer that does not belong to you, you should specify 1 to duplicate the buffer.

Parsing Strings

The two examples in the preceding section parse only a single parameter each. In fact, zend_parse_parameters() provides great flexibility in parameter parsing by allowing you to specify a format string that describes the complete set of expected parameters. Table 21.2 shows the format characters, the types they describe, and the actual user-defined C variable types each format fills out.

Table 21.2. zend_parse_parameters() Format Strings

Format

Type

Takes

l

Long integer

long *

d

Floating-point number

double *

s

String

(char **, int *)

b

Boolean

zend_bool *

r

PHP resource

zval **

a

Array

zval **

o

Object

zval **

O

Object (of a specific type)

zval **, type name

z

zval

zval **


For example, to specify that a function takes two strings and a long, you would use this:

PHP_FUNCTION(strncasecmp)
{
  char *string1, *string2;
  int string_length1, string_length2;
  long comp_length;

  if(zend_parse_parameters(ZEND_NUM_ARG() TSRMLS_CC, "ssl",
                           &string1, &string_length1,
                           &string2, &string_length2,
                           &comp_length) {
    return;
  }
  /* ... */
}

This example specifies a char **/int * pair for each string and a long * for the long.

In addition, you can specify format string modifiers that allow you to specify optional arguments by using parameter modifiers (see Table 21.3).

Table 21.3. zend_parse_parameters() Parameter Modifiers

Parameter Modifiers

Description

|

Everything after a | is an optional argument.

!

The preceding parameter can be a specified type or NULL. If NULL is passed, the associated C pointer is also set to NULL. This is valid only for the types that return zvalstypes a, o, O, r, and z.

/

The preceding parameter should be separated, meaning that if its reference count is greater than 1, its data should be copied into a fresh zval. This is good to use if you are modifying a zval (for example, doing a forced-type conversion) and do not want to affect any other users. This modifier is usable only for types a, o, O, r, and z.


Other Return Macros

You have already seen two of the return macros, RETURN_STRINGL and RETURN_LONG, which set the value of return_value and return. Table 21.4 shows the full range of return macros.

Table 21.4. Return Macros

Macro

Description

RETURN_BOOL(zend_bool value)

Sets return_value from a Boolean value value.

RETURN_NULL()

Sets return_value to null.

RETURN_TRUE()

Sets return_value to TRue.

RETURN_FALSE()

Sets return_value to false.

RETURN_LONG(long value)

Sets return_value from the long integer value.

RETURN_DOUBLE(double value)

Sets return_value from the double value.

RETURN_EMPTY_STRING()

Sets return_value to the empty string "".

RETURN_STRING(char *string, int duplicate)

Sets return_value from the character buffer string and a flag to indicate whether the buffer memory should be used directly or copied. This is not binary safe; it uses strlen() to calculate the length of string.

RETURN_STRINGL(char *string, int length, int duplicate)

Sets return_value from the character buffer string of the specified length length and a flag to indicate whether the buffer memory should be used directly or copied. This is binary safe.


Manipulating Types

To understand how to set more complex values for return_value, you need to better understand how to manipulate zvals. As described in Chapter 20, variables in PHP are all represented by the zval type, which is a composite of all the possible PHP base types. This strategy permits PHP's weak and dynamic typing semantics, as is described in Chapter 20.

When you want to create a variable that will be manipulated within PHP, that variable needs to be a zval. The normal creation process is to declare it and allocate it with a built-in macro, as in the following example:

zval *var;
MAKE_STD_ZVAL(var);

This allocates val and correctly sets its reference counters.

After the zval has been created, you can assign to it. For simple types (numbers, strings, Booleans), there are simple macros for this:

    ZVAL_NULL(zval *var);
    ZVAL_BOOL(zval *var, zend_bool value)
    ZVAL_LONG(zval *var, long value)
    ZVAL_DOUBLE(zval *var, double value)
    ZVAL_EMPTY_STRING(zval *var)
    ZVAL_STRINGL(zval *var, char *string, int length, int duplicate)
    ZVAL_STRING(zval *var, char *string, int duplicate)

These macros look very similar to the similarly named RETURN_ macros. They share identical assignment semantics. These macros all set scalar variables. To create an array, you use the following code:

zval *array;
MAKE_STD_ZVAL(array);
array_init(array);

Now array is an empty array zval. Much like regular zvals, there are convenience methods for adding simple types to arrays:

add_assoc_long(zval *arg, char *key, long value);
add_assoc_bool(zval *arg, char *key, int value);
add_assoc_resource(zval *arg, char *key, int value);
add_assoc_double(zval *arg, char *key, double value);
add_assoc_string(zval *arg, char *key, char *string, int duplicate);
add_assoc_stringl(zval *arg, char *key, char *string,
               int string_length, int duplicate);
add_assoc_zval(zval *arg, char *key, zval *value);

All these except the last should be relatively obvious: They support automatically adding base types to an array, keyed by the specified key. These functions uniformly return SUCCESS on success and FAILURE on failure.

For example, to create a C function that is identical to this PHP function:

function colors()
{
  return array("Apple"     => "Red",
               "Banana"    => "Yellow",
               "Cranberry" => "Maroon");
}

you would write this:

PHP_FUNCTION(colors)
{
  array_init(return_value);
  add_assoc_string(return_value, "Apple", "Red", 1);
  add_assoc_string(return_value, "Banana", "Yellow", 1);
  add_assoc_string(return_value, "Cranberry", "Maroon", 1);
  return;
}

Note the following:

  • return_value is allocated outside PHP_FUNCTION, so it does not need to be acted on by MAKE_STD_ZVAL.

  • Because return_value is passed in, you do not return it at the end of the function; you simply use return.

  • Because the string values being used ("Red", "Yellow", "Maroon") are stack-allocated buffers, you need to duplicate them. Any memory not allocated with emalloc() should be duplicated if used to create a string zval.

The add_assoc_zval() function allows you to add an arbitrary zval to an array. This is useful if you need to add a nonstandard type, to create, for instance, a multidimensional array. The following PHP function generates a simple multidimensional array:

function people()
{
  return array(
    'george' => array('FullName' => 'George Schlossnagle',
                      'uid'      => 1001,
                      'gid'      => 1000),
    'theo'   => array('Fullname' => 'Theo Schlossnagle',
                      'uid'      => 1002,
                      'gid'      => 1000));
}

To duplicate this functionality in C, you create a fresh array for george and then add its zval to return_value. Then you repeat this for theo:

PHP_FUNCTION(people)
{
  zval *tmp;

  array_init(return_value);

  MAKE_STD_ZVAL(tmp);
  array_init(tmp);
  add_assoc_string(tmp, "FullName", "George Schlossnagle", 1);
  add_assoc_long(tmp, "uid", 1001);
  add_assoc_long(tmp, "gid", 1000);
  add_assoc_zval(return_value, "george", tmp);

  MAKE_STD_ZVAL(tmp);
  array_init(tmp);
  add_assoc_string(tmp, "FullName", "Theo Schlossnagle", 1);
  add_assoc_long(tmp, "uid", 1002);
  add_assoc_long(tmp, "gid", 1000);
  add_assoc_zval(return_value, "theo", tmp);
  return;

Note that you can reuse the pointer tmp; when you call MAKE_STD_ZVAL(), it just allocates a fresh zval for your use.

There is a similar set of functions for dealing with indexed arrays. The following functions work like the PHP function array_push(), adding the new value at the end of the array and assigning it the next available index:

add_next_index_long(zval *arg, long value);
add_next_index_null(zval *arg);
add_next_index_bool(zval *arg, int value);
add_next_index_resource(zval *arg, int value);
add_next_index_double(zval *arg, double value);
add_next_index_string(zval *arg, char *str, int duplicate);
add_next_index_stringl(zval *arg, char *str, uint length, int duplicate);
add_next_index_zval(zval *arg, zval *value);

If you want to insert into the array at a specific index, there are convenience functions for that as well:

add_index_long(zval *arg, uint idx, long value);
add_index_null(zval *arg, uint idx);
add_index_bool(zval *arg, uint idx, int value);
add_index_resource(zval *arg, uint idx, int value);
add_index_double(zval *arg, uint idx, double value);
add_index_string(zval *arg, uint idx, char *string, int duplicate);
add_index_stringl(zval *arg, uint idx, char *string,
                  int string_length, int duplicate);
add_index_zval(zval *arg, uint index, zval *value);

Note that in the case of both the add_assoc_ and add_index_ functions, any existing data with that key or index will be overwritten.

You now know all you need to know to be able to create arrays, but how do you extract data from them in a script? As discussed in Chapter 20, one of the types represented by a zval is the HashTable type. This is used for both associative and indexed arrays in PHP. To gain access to a zval's hashtable, you use the HASH_OF() macro. Then you utilize the hash iteration functions to handle the resulting hashtable.

Consider the following PHP function, which is designed as a rudimentary version of array_filter():

function array_strncmp($array, $match)
{
  foreach ($array as $key => $value) {
    if( substr($key, 0, length($match)) == $match ) {
      $retval[$key] = $value;
    }
  }
  return $retval;
}

A function of this nature is useful, for example, when you're trying to extract all the HTTP headers for a request. In C this looks as follows:

PHP_FUNCTION(array_strncmp)
{
  zval *z_array, **data;
  char *match;
  char *key;
  int match_len;
  ulong index;
  HashTable *array;
  if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "as",
                           &z_array, &match, &match_len) == FAILURE) {
    return;
  }
  array_init(return_value);
  array = HASH_OF(z_array);
  zend_hash_internal_pointer_reset(array);
  while(zend_hash_get_current_key(array, &key, &index, 0) == HASH_KEY_IS_STRING) {
if(!strncmp(key, match, match_len)) {
      zend_hash_get_current_data(array, (void**)&data);     zval_add_ref(data);
       add_assoc_zval(return_value, key, *data);
    }
    zend_hash_move_forward(array);
  }
}

There is a good bit of new material in this function. Ignore the zval manipulation for the moment; you'll learn more on that shortly. The important part of this example for now is the process of iterating over an array. First, you access the array's internal hashtable, using the HASH_OF() macro. Then you reset the hashtable's internal iterator by using zend_hash_internal_pointer_reset(). This is akin to calling reset($array); in PHP.

Next, you access the current array's key with zend_hash_get_current_key(). This takes the HashTable pointer, a char ** for the keyname, and an ulong * for the array index. You need to pass both pointers in because PHP uses a unified type for associative and indexed arrays, so an element may either be indexed or keyed. If there is no current key (for instance, if you have iterated through to the end of the array), this function returns HASH_KEY_NON_EXISTENT; otherwise, it returns either HASH_KEY_IS_STRING or HASH_KEY_IS_LONG, depending on whether the array is associative or indexed.

Similarly, to extract the current data element, you use zend_hash_get_current_data(), which takes the HashTable pointer and a zval ** to hold the data value. If an array element matches the condition for copying, the zvals reference count is incremented with zval_add_ref(), and it is inserted into the return array. To advance to the next key, you use zend_hash_move_forward().

Type Testing Conversions and Accessors

As described in Chapter 20, zvals are actually a composite of primitive C data types represented by the zvalue_value union:

typedef union _zvalue_value {
  long lval;
  double dval;
  struct {
    char *val;
    int len;
  } str;
  HashTable *ht;
  zend_object_value obj;
} zvalue_value;

PHP provides accessor macros that allow access to these component values. Because this is a union, only a single representation is valid at one time. This means that if you want to use an accessor to access the zval as a string, you first need to ensure that it is currently represented as a string.

To convert a zval to a given type, you can use the following functions:

convert_to_string(zval *value);
convert_to_long(zval *value);
convert_to_double(zval *value);
convert_to_null(zval *value);
convert_to_boolean(zval *value);
convert_to_array(zval *value);
convert_to_object(zval *value);

To test whether your zval needs conversion, you can use the Z_TYPE_P() macro to check the zval's current type, as demonstrated in the following example:

PHP_FUNCTION(check_type)
{
  zval *value;
  char *result;
  if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &value) == FAILURE){
    return;
  }
  switch(Z_TYPE_P(value)) {
    case IS_NULL:
      result = "NULL";
      break;
    case IS_LONG:
      result = "LONG";
      break;
    case IS_DOUBLE:
      result = "DOUBLE";
      break;
    case IS_STRING:
      result = "STRING";
      break;
    case IS_ARRAY:
      result = "ARRAY";
      break;
    case IS_OBJECT:
      result = "OBJECT";
      break;
    case IS_BOOL:
      result = "BOOL";
      break;
    case IS_RESOURCE:
      result = "RESOURCE";
      break;
    case IS_CONSTANT:
      result = "CONSTANT";
      break;
    case IS_CONSTANT_ARRAY:
      result = "CONSTANT_ARRAY";
      break;
    default:
      result = "UNKNOWN";
  }
  RETURN_STRING(result, 1);
}

To then access the data in the various types, you can use the macros in Table 21.5, each of which takes a zval.

Table 21.5. zval-to-C Data Type Conversion Macros

Macro

Returns

Description

Z_LVAL

long

Returns a long value

Z_BVAL

zend_bool

Returns a Boolean value

Z_STRVAL

char *

Returns a buffer for the string

Z_STRLEN

int

Returns the length of a string

Z_ARRVAL

HashTable

Returns an internal hashtable

Z_RESVAL

long

Returns the resource handle


In addition, there are forms of all these macros to accept zval * and zval ** pointers. They are named identically, but with an appended _P or _PP, respectively. For instance, to extract the string buffer for zval **p, you would use Z_STRVAL_PP(p).

When data is passed into a function via the zend_parse_parameters() function, the resulting data is largely safe for use. When you get access to data as a zval, however, all bets are off. The problem lies in the way zvals in PHP are reference counted. The Zend Engine uses a copy-on-write semantic, which means if you have code like the following, you actually only have a single zval with a reference count of two:

$a = 1;
$b = $a;

If you modify $b in your PHP code, $b is automatically separated into its own zval. Inside an extension, though, you need to perform this separation yourself. Separation takes a zval pointer whose reference count is greater than one and copies its content into a new zval. This means that you can manipulate its contents at your whim without worrying about affecting anyone else's copy. Separating a zval is prudent if you are going to perform type conversion.

Separation is performed with the SEPARATE_ZVAL() macro. Because you often may not want to separate a zval if it is accessed by reference, there is also a SEPARATE_ZVAL_IF_NOT_REF() macro that performs the separation only if the zval is a reference to another zval.

Finally, sometimes you might want to create a new copy of a variable, as in this example:

$a = $b;

For strings and numeric scalars, this copy might seem silly; after all, it is quite easy to create a brand-new zval from a char * or a long. Copying is especially essential when it comes to complex data types, such as arrays or objects, in which case copying would be a multistep operation.

You might naively assume that if you wanted to write a function that returns its single parameter unchanged, you could use this:

PHP_FUNCTION(return_unchanged)
{
  zval *arg;
  if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arg) == FAILURE)
  {
    return;
  }
  *return_value = *arg;
  return;
}

However, performing this sort of copy creates an invalid reference to the data pointed at by arg. To correctly perform this copy, you also need to invoke zval_copy_ctor(). zval_copy_ctor() is modeled after an object-oriented style copy constructor (like the _ _clone() method in PHP 5) and handles making proper deep copies of zvals, regardless of their type. The preceding return_unchanged() function should correctly be written as follows:

PHP_FUNCTION(return_unchanged)
{
  zval *arg;
  if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arg) == FAILURE)
  {
    return;
  }
  *return_value = *arg;
  zval_copy_ctor(return_value);
  return;
}

Similarly, you might from time to time be required to destroy a zvalfor example, if you create a temporary zval inside a function that is not returned into PHP. The same complexities that make copying a zval difficultthe deep and variable structuresmake destroying a zval difficult as well. For this you should use the zval destructor zval_dtor().

Using Resources

You use resources when you need to assign an arbitrary data type to a PHP variable. By arbitrary, I don't mean a string or number or even an array, but a generic C pointer that could correspond to anything. Resources are often used for database connections, file pointers, and other resources that you may want to pass between functions but that do not correspond to any of PHP's native types.

Creating resources in PHP is a rather complicated process. In PHP, actual resource values are not stored in zvals. Instead, resources are handled similarly to objects: An integer that identifies the resource is stored in the zval and can be used to find the actual data pointer for the resource in a resource data storage list. Object-oriented extensions are covered in Chapter 22, "Extending PHP: Part II."

To start handling resources, you need to create a list to store the resource values. List registration is performed with the function zend_register_list_destructors_ex(), which has the following prototype:

int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld,
                                char *type_name, int module_number);

ld is a function pointer that takes a zend_rsrc_list_entry * structure and handles destruction of a nonpersistent resource. For example, if the resource is a pointer to a database connection, ld would be a function that rolls back any uncommitted transactions, closes the connection, and frees any allocated memory. Nonpersistent resources are destroyed at the end of every request.

The zend_rsrc_list_entry data type looks like this:

typedef struct _zend_rsrc_list_entry {
        void *ptr;
        int type;
        int refcount;
} zend_rsrc_list_entry;

pld is identical to ld, except that it is used for persistent resources. Persistent resources are not automatically destroyed until server shutdown. When registering resource lists in practice, you traditionally create one list for nonpersistent resources and one for persistent resources. This is not technically necessary, but it adds to the orderliness of your extension and is the traditional method for handling resources.

type_name is a string used to identify the type of resource contained in the list. This name is used only for making user errors pretty and serves no technical function for the resources.

module_number is the internal number used to identify the current extension. One of the elements of zend_module_entry is zend_module_entry.module_number. When PHP loads the extension, it sets this module number for you. module_number is what you pass as the fourth parameter to zend_register_list_destructors_ex().

If you want to register a POSIX file handle as a resource (similar to what fopen does under PHP 4), you need to create a destructor for it. This destructor would simply close the file handle in question. Here is a destructor function for closing POSIX file handles: static void posix_fh_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)

{
   if (rsrc->ptr) {
     fclose(rsrc->ptr);
     rsrc->ptr = NULL;
   }
}

The actual registration is performed in the PHP_MINIT_FUNCTION() handler. You start by defining a static int for each list you need to create. The int is a handle to the list and how you reference it. The following code creates two lists, one persistent and one not:

static int non_persist;
static int persist;

PHP_MINIT_FUNCTION(example)
{
  non_persist = zend_register_list_destructors_ex(posix_fh_dtor, NULL,
                                                  "non-persistent posix fh",
                                                  module_number);
  persist = zend_register_list_destructors_ex(NULL, posix_fh_dtor,
                                              "persistent posix fh",
                                               module_number);
  return SUCCESS;
}

To actually register a resource you use the following macro:

ZEND_REGISTER_RESOURCE(zval *rsrc_result, void *ptr, int rsrc_list)

This inserts the data pointer ptr into the list rsrc_list, returns the resource ID handle for the new resource, and makes the zval rsrc_result a resource that references that handle. rsrc_result can also be set to NULL if you prefer to assign the handle into something other than an existing zval.

The following is a function that (very roughly) models fopen() and registers its FILE pointer as a persistent resource:

PHP_FUNCTION(pfopen)
{
  char *path, *mode;
  int path_length, mode_length;
  FILE *fh;
  if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",
                           &path, &path_length,
                           &mode, &mode_length) == FAILURE) {
    return;
  }
  fh = fopen(path, mode);
  if(fh) {
    ZEND_REGISTER_RESOURCE(return_value, fh, persist);
    return;
  }
  else {
    RETURN_FALSE;
  }
}

Of course, a function that blindly creates persistent resources isn't very interesting. What it should be doing is seeing whether a current resource exists, and if so, it should use the preexisting resource instead of creating a new one.

There are two ways you might look for a resource. The first is to look for a resource, given the general initialization parameters. This is the crux of persistent resources. When you begin to establish a new persistent resource, you see whether a similarly declared resource already exists. Of course, the difficulty here is that you have to conceive of a keyed hashing system based on the initialization parameters to find your resource. In contrast, if you have a resource value assigned to a zval, then you already have its resource ID, so retrieval should (hopefully) be much simpler.

To find resources by ID, you need both a hash and a key. PHP provides the key: the global HashTable EG(persistent_list) is used for looking up resources by key. For the key, you are on your own. In general, a resource is uniquely determined by its initialization parameters, so a typical approach is to string together the initialization parameters, perhaps with some namespacing.

Here is a reimplementation of pfopen(), which proactively looks in EG(persistent_list) for a connection before it creates one:

PHP_FUNCTION(pfopen)
{
  char *path, *mode;
   int path_length, mode_length;
  char *hashed_details;
   int hashed_details_length;
  FILE *fh;
  list_entry *le;
  if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",
                           &path, &path_length,
                           &mode, &mode_length) == FAILURE) {
    return;
  }
  hashed_details_length = strlen("example_") + path_length + mode_length;
  hashed_details = emalloc(hashed_details_length + 1);
  snprintf(hashed_details, hashed_details_length + 1,
           "example_%s%s", path, mode);
  if(zend_hash_find(&EG(persistent_list), hashed_details,
                    hashed_details_length + 1, (void **) &le) == SUCCESS) {
    if(Z_TYPE_P(le) != persist) {
      /* not our resource */
      zend_error(E_WARNING, "Not a valid persistent file handle");
      efree(hashed_details);
      RETURN_FALSE;
    }
    fh = le->ptr;
  }
  else {
    fh = fopen(path, mode);
    if(fh) {
      list_entry new_le;
      Z_TYPE(new_le) = persist;
      new_le.ptr = fh;
      zend_hash_update(&EG(persistent_list), hashed_details,
                       hashed_details_length+1, (void *) &new_le,
                       sizeof(list_entry), NULL);
    }
  }
 efree(hashed_details);
 if(fh) {
   ZEND_REGISTER_RESOURCE(return_value, fh, persist);
   return;
  }
  RETURN_FALSE;
}

You should notice the following about the new pfopen() function:

  • You store new_le of type list_entry, which is identical to the type zend_rsrc_list_entry in EG(persistent_list). This convention is a convenient structure to use for this purpose.

  • You set and check that the type of new_le is the resource list ID. This protects against potential segfaults due to naming conflicts that can occur if another extension chooses an identical namespacing scheme (or you choose not to namespace your hashed_details string).

If you are using neither concurrent access resources (where two initialization calls might correctly return the same resource) nor persistent resources, you do not need to worry about storing information in the persistent list. Accessing data by its instantiation parameters is the hard way of doing things and is necessary only when you are (possibly) creating a new resource.

In most functions, you are handed a resource handle zval, and you need to extract the actual resource for it. Fortunately, doing so is very easy. If you are looking in a single list, you can use the following macro:

ZEND_FETCH_RESOURCE(void *rsrc_struct, rsrc_struct_type, zval **zval_id,
                 int default_id, char *name, int rsrc_list);

These are the arguments of ZEND_FETCH_RESOURCE():

  • rsrc_struct is the actual pointer you want the resource data to be stored in.

  • rsrc_struct_type is the type of struct the resource is (for example, FILE *).

  • zval_id is a zval of resource type that contains the resource ID.

  • default_id is an integer that specifies the default resource to use. A common use for this is to store the last accessed resource ID in an extension's globals. Then, if a function that requires a resource does not have one passed to it, it simply uses the last resource ID. If -1 is used, no default is attempted.

  • name is a character string that is used to identify the resource you were seeking. This string is used only in information warning messages and has no technical purpose.

  • rsrc_list is the list that should be searched for the resource.

If the resource fetch fails, a warning is generated, and the current function returns NULL.

The following is the function pfgets(), which reads a line from a file resource created by pfopen():

PHP_FUNCTION(pfgets)
{
  char *out;
  int length = 1024;
  zval *rsrc;
  FILE *fh;
  if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r|l", &rsrc, &length)
       == FAILURE) {
    return;
  }
  ZEND_FETCH_RESOURCE(fh, FILE *, rsrc, -1, "Persistent File Handle", persist);
  out = (char *) emalloc(length);
  fgets(out, length, fh);
  RETURN_STRING(out, 0);
}

Returning Errors

Generating procedural errors in extension code is almost identical to generating errors in PHP. Instead of calling trigger_error() in PHP, you can use zend_error() in C. zend_error() has the following API:

zend_error(int error_type, char *fmt, ...);

error_type is the full range of errors enumerated in Chapter 3, "Error Handling." Otherwise, the API is identical to the printf() family of functions. The following function generates a warning:

zend_error(E_WARNING, "Hey this is a warning");

Remember that if you use E_ERROR, the error is fatal, and script execution is stopped. (Chapter 23, "Writing SAPIs and Extending the Zend Engine," describes how to override this behavior).

Throwing exceptions is covered in detail in Chapter 22, which looks at object-oriented extensions in detail.

Using Module Hooks

In addition to enabling you to define and export function definitions, PHP also gives extensions the ability to run code in response to certain events in the PHP runtime. These events include the following:

  • Module startup

  • Module shutdown

  • Request startup

  • Request shutdown

  • phpinfo registration

When you create a module, one of the required components is zend_module_entry, which looks like this:

zend_module_entry example_module_entry = {
    STANDARD_MODULE_HEADER,
    "example",
    example_functions,
    PHP_MINIT(example),
    PHP_MSHUTDOWN(example),
    PHP_RINIT(example),
    PHP_RSHUTDOWN(example),
    PHP_MINFO(example),
    VERSION,
    STANDARD_MODULE_PROPERTIES
};

The third member of this structure, example_functions, specifies the array of functions that will be registered by the extension. The rest of the structure declares the callbacks that will be executed by the various module hooks.

Module Startup and Shutdown

An extension's module initialization and shutdown hooks are called when the extension is loaded and unloaded, respectively. For most extensions (those that are either compiled statically into PHP or loaded via an INI setting), module initialization happens once, at server startup. Module shutdown is similarly called during server shutdown. In the Apache 1.3 (or Apache 2 prefork MPM), this hook is called before any children are forked off. Thus, it is an ideal place to create or initialize any sort of global or shared resource, and it's a poor place to initialize any resource that cannot be shared between processes.

The module initialization hook is registered via the following function:

PHP_MINIT_FUNCTION(example)
{
  return SUCCESS;
}

In general, module initialization is the ideal place to define constants, initialize global data structures, and register and parse INI options.

Defining Constants

Because constants are immutable, they should be created during module initialization. In contrast to userspace PHP, where using a define() is not very different performance-wise from using global variables, defining constants in extension code is a clear win. This is because extension constants (such as functions and classes) do not need to be reinstated between requests (although you can specify them to be destroyed at request end). This means that declaring even a large number of constants is basically free.

To define a constant, you can use the following macros:

   REGISTER_LONG_CONSTANT(name, value, flags)
   REGISTER_DOUBLE_CONSTANT(name, value, flags)
   REGISTER_STRING_CONSTANT(name, string, flags)
   REGISTER_STRNIG_CONSTANT(name, string, string_length, flags)

These are the possible flags for the macros:

  • CONST_CS Constant is case-sensitive.

  • CONST_PERSISTENT Constant should persist across requests.

Obviously, if you are defining constants during module initialization, you must specify CONST_PERSISTENT. Unless you have specific reasons that you need to use conditional defines, you should define your constants as persistent and register them during module initialization. Constants defined in userspace PHP are case-sensitive, so for PHP-like behavior you should use CONST_CS as well.

The following is an example of a MINIT function in the sample extension that defines two constants:

PHP_MINIT_FUNCTION(example)
{
  REGISTER_LONG_CONSTANT("EXAMPLE_VERSION",
                         VERSION,
                         CONST_CS | CONST_PERSISTENT);
  REGISTER_STRING_CONSTANT("BUILD_DATE",
                           "2004/01/03",
                           CONST_CS | CONST_PERSISTENT);
  return SUCCESS;
}

Enabling Globals

Most extensions carry around a few global variables, which often hold default connection data, global resources, and behavioral toggles. It is easy to implement globals without using the Zend macros, but those macros are primarily useful for automatically making globals thread-safe.

To start with, you use the ZEND_BEGIN_MODULE_GLOBALS and ZEND_END_MODULE_GLOBALS macros to define a struct that holds global variables:

ZEND_BEGIN_MODULE_GLOBALS(example)
  char *default_path;
  int default_fd;
  zend_bool debug;
ZEND_END_MODULE_GLOBALS(example)

These macros either create a plain struct zend_example_globals with these elements or a set of thread-safe structs with these elements, depending on whether PHP has been compiled with thread safety. Because the resultant structs will need to be accessed differently, you should also create a conditional accessor that uses the correct access method, depending on PHP's thread-safety situation:

#ifdef ZTS
#define ExampleG(v) TSRMG(example_globals_id, zend_example_globals *, v)
#else
#define ExampleG(v) (example_globals.v)
#endif

You should always then access globals as follows:

char *path = ExampleG(default_path);

To initialize globals, you create an initialization and destruction function, like this:

static void example_init_globals(zend_example_globals *example_globals)
{
  example_globals->default_path = NULL;
}

static void example_destroy_globals(zend_example_globals *example_globals)
{
}

Then, during the MINIT phase, you perform the registration via the ZEND_INIT_MODULE_GLOBALS() macro, as shown here:

PHP_MINIT_FUNCTION(example)
{
ZEND_INIT_MODULE_GLOBALS(example, example_init_globals, example_destroy_globals);
/* ... */
}

This destructor function is usually used when there are complex data types (such as a hashtable) that need to be cleaned on shutdown. If you do not need to register a destructor, you can simply pass NULL into the macro.

Parsing INI Entries

One thing that you can do in extensions that is impossible in userspace PHP code is registering and acting on php.ini settings. INI settings are useful for a couple reasons:

  • They provide global settings, independent of scripts.

  • They provide access controls on settings that can restrict developers from changing the INI settings in their scripts.

  • They allow for configuration of module hooks that are called before any scripts are run (during MINIT and RINIT, for instance).

PHP provides a set of macros for easy registration of INI directives. First, in the main body of the C file, you add a macro block, like this:

PHP_INI_BEGIN()
/* ini specifications go here ... */
PHP_INI_END()

This defines an array of zend_ini_entry entries. Inside the block you make your INI declarations via the following macro:

STD_PHP_INI_ENTRY(char *ini_directive, char *default_value,
                  int location, int type, struct_member,
                  struct_ptr, struct_property)

"ini_directive" is the full name of the INI directive that you are creating. It is a polite convention to namespace INI directives to avoid potential conflicts. For example, if you want to create an enabled setting for the sample extension, you should name it example.enabled.

default_value specifies the default value for the INI directive. Because INI values are set as strings in the php.ini file, the default value must be passed as a string, even if it is numeric. This value is copied, so using a statically allocated value is fine.

location specifies the places where a user can set the value of the directive. These places are defined as constants and can of course be combined with the bitwise OR operator. The following are acceptable bit settings for location:

Setting

Description

PHP_INI_USER

Entry can be set in user scripts via ini_set().

PHP_INI_PERDIR

Entry can be set in php.ini, .htaccess, or httpd.conf. In the .htaccess or httpd.conf file, it can be applied on a per-directory basis.

PHP_INI_SYSTEM

Entry can be set in php.ini or httpd.conf. The setting is serverwide.

PHP_INI_ALL

Entry can be set anywhere. This is equivalent to PHP_INI_USER|PHP_INI_PERDIR|PHP_INI_SYSTEM.


type is a function name that specifies how to handle modifications to the INI directive (via php.ini, .htaccess, httpd.conf, or ini_set()). The following are the standard functions that can be used in this macro:

Function

Destination C Type

OnUpdateBool

zend_bool

OnUpdateLong

long

OnUpdateReal

double

OnUpdateString

char *

OnUpdateStringUnempty

char *


These functions are aptly named and should be self-explanatory. OnUpdateStringUnempty fails if an empty string is passed to it. Otherwise, it is identical to OnUpdateString.

INI values are almost always stored in extension globals. This makes sense because for an individual script, the INI values are globally set. (Even when you change them using ini_set(), you are effecting a global change.) In threaded environments, INI values are stored in thread local globals, so modification of an INI value affects only the value for that specific thread. To specify which global variable the setting should be stored in, you pass the final 3 bits of information.

struct_type specifies the type of the structure you will be setting the value into. In the normal case, where this is the globals structure you created with ZEND_BEGIN_MODULE_GLOBALS(example), this type would be zend_example_globals.

struct_ptr gives the specific instance of the type struct_type that should be modified. In the usual case, where globals are declared via the built-in macros, this is example_globals.

Finally, struct_property notes the element of the struct struct_name to modify.

In the case of an integer value set, the STD_PHP_INI_ENTRY() macro roughly translates into the following C code:

 (struct_type *)struct_ptr->struct_property = default_value;

The following is an example that allows setting of the default_path global in the sample extension via the INI directive example.path:

PHP_INI_BEGIN()
  STD_PHP_INI_ENTRY("example.path", NULL, PHP_INI_PERDIR|PHP_INI_SYSTEM,
                 OnUpdateString, default_path, zend_example_globals,
                 example_globals)
  STD_PHP_INI_ENTRY("example.debug", "off", PHP_INI_ALL, OnUpdateBool,
                 debug, zend_example_globals, example_globals)
PHP_INI_END()

The default path will be set to NULL, and access to this variable will only be allowed from the php.ini, httpd.conf, or .htaccess files. It also allows you to set debug, with a default value of off, from anywhere.

To then register these entries, you call REGISTER_INI_ENTRIES() in the MINIT function, as follows:

PHP_MINIT_FUNCTION(example)
{
  ZEND_INIT_MODULE_GLOBALS(example, example_init_globals,
                       example_destroy_globals);
  REGISTER_INI_ENTRIES();
}

If you want to access the values in the code (via ini_get()), you can use a number of macros, which fetch the INI values as specified C types. The macros are broken into two groups. The first set, shown in Table 21.6, returns the current value of the macro.

Table 21.6. Current INI Setting Accessors

Macro

Return C Type

INI_BOOL(name)

zend_bool

INI_INT(name)

long

INI_FLT(name)

double

INI_STR(name)

char *


The second set of macros, shown in Table 21.7, returns the original value of the macro, before any modification via httpd.conf, .htaccess, or ini_set().

Table 21.7. Original INI Setting Accessors

Macro

Return C Type

INI_BOOL_ORIG(name)

zend_bool

INI_INT_ORIG(name)

long

INI_FLT_ORIG(name)

double

INI_STR_ORIG(name)

char *


Module Shutdown

If you have registered INI entries during MINIT, it is appropriate to unregister them during shutdown. You can do this via the following code:

PHP_MSHUTDOWN_FUNCTION(example)
{
  UNREGISTER_INI_ENTRIES();
}

Request Startup and Shutdown

In addition to module startup and shutdown, PHP also provides hooks that are called at the beginning and end of each request. The request initialization (RINIT) and shutdown (RSHUTDOWN) hooks are useful for creating and destroying per-request data.

Request Startup

Often you have resources that will be used in every request and that should always start at a consistent state. For example, ExampleG(default_path) may correspond with a file that needs to be opened at the beginning of every request and closed at the end (for example, a debugging log private to the extension and whose path can be set in an .htaccess file, thus making a persistent resource impractical). In that case, you might want to open the log at the beginning of every request and exit with an error if this is not possible.

The code to perform this logic is placed in a PHP_RINIT_FUNCTION() block. At the beginning of every distinct request, PHP calls this function. If the function does not return SUCCESS, the request ends with a fatal error. The following is a request startup function that opens a default file at the beginning of every request:

PHP_RINIT_FUNCTION(example)
{
  if(ExampleG(default_path)) {
    ExampleG(default_fd) = open(ExampleG(default_path), O_RDWR|O_CREAT, 0);
    if(ExampleG(default_fd) == -1) {
      return FAILURE;
    }
  }
  return SUCCESS;
}

Request Shutdown

Request shutdown is the ideal place to close any resources that you need to make sure are destroyed at the end of a script. It is also an ideal place to ensure that the extension's state is set back to where it should be before a new request. PHP_RSHUTDOWN_FUNCTION() declares this hook.

In the following example, the sample extension needs to clean its logfile at request end:

PHP_RSHUTDOWN _FUNCTION(example) {
  if(ExampleG(default_fd) > -1) {
    close(ExampleG(default_fd));
    ExampleG(default_fd) = -1;
  }
  return SUCCESS;
}

The extension needs to close the file descriptor ExampleG(default_fd) that it opened during RINIT. If you wanted to leave it open, you could, and it would persist across requests. Because it can be set on a per-directory basis via .htaccess rules, leaving it open in this case is impractical.

As in RINIT, this function must return SUCCESS, or the request will terminate with a fatal error.

phpinfo() Registration

PHP extensions are able to register themselves with phpinfo(), so that their status and configuration can be displayed.

The PHP_MINFO_FUNCTION() function is registered with the PHP_MINFO() macro:

zend_module_entry mysql_module_entry = {
  STANDARD_MODULE_HEADER,
  "example",
  example_functions,
  PHP_MINIT(example),
  PHP_MSHUTDOWN(example),
  PHP_RINIT(example),
  PHP_RSHUTDOWN(example),
  PHP_MINFO(example),
  VERSION,
  STANDARD_MODULE_PROPERTIES
};

PHP_MINFO_FUNCTION() is basically a CGI script that outputs certain informationusually an HTML table that lists the function's status and certain configuration information. To ease output formatting and support both plain-text and HTML phpinfo() formats, you should use the built-in functions to generate output. The following is a simple MINFO block that just notes that the sample extension is enabled:

PHP_MINFO_FUNCTION(example)
{
  php_info_print_table_start();
  php_info_print_table_row(2, "Example Extension", "enabled");
  php_info_print_table_end();
}

The php_info_print_table_row() function takes the number of columns and a string for each one.


Previous
Table of Contents
Next