Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Advanced PHP Programming

.pdf
Скачиваний:
71
Добавлен:
14.04.2015
Размер:
7.82 Mб
Скачать

528 Chapter 21 Extending PHP: Part I

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():

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

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

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

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

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

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

ed 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);

}

Extension Basics

529

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-ori- ented 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:

nModule startup

nModule shutdown

nRequest startup

nRequest shutdown

nphpinfo 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

};

530Chapter 21 Extending PHP: Part I

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

nCONST_CS—Constant is case-sensitive.

nCONST_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

Extension Basics

531

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

532Chapter 21 Extending PHP: Part I

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:

nThey provide global settings, independent of scripts.

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

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

Extension Basics

533

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

534 Chapter 21 Extending PHP: Part I

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

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

 

 

Extension Basics

535

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

536 Chapter 21 Extending PHP: Part I

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

};

An Example: The Spread Client Wrapper

537

PHP_MINFO_FUNCTION()is basically a CGI script that outputs certain information—usual- ly 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.

An Example: The Spread Client Wrapper

You now have all the tools you need to build a procedural interface PHP extension in C.To tie all these parts together, a full example is called for.

Chapter 15,“Building a Distributed Environment,” shows an implementation of a distributed cache management system that uses Spread. Spread is a group communication toolkit that allows members to join a set of named groups and receive messages for those groups by using certain semantics (for example, that every member in the group will receive all messages in the same order as every other member).These strong rules provide an excellent mechanism for tackling distributed tasks, such as building multireader distributed logging systems, master–master database replication, or, as in the case just shown, reliable messaging systems between multiple participants.

The Spread library presents a very simple C API, so it is an ideal example for writing a PHP extension around.The following parts of the C API are covered here:

int

SP_connect( const char *spread_name, const char *private_name,

 

int priority, int group_membership, mailbox *mbox,

 

char *private_group );

int

SP_disconnect( mailbox mbox );

int

SP_join( mailbox mbox, const char *group );

int

SP_multicast( mailbox mbox, service service_type,

 

const char *group,

 

int16 mess_type, int mess_len, const char *mess );

int

SP_multigroup_multicast( mailbox mbox, service service_type,

 

int num_groups,

 

const char groups[][MAX_GROUP_NAME],

 

int16 mess_type,

 

const scatter *mess );

int

SP_receive( mailbox mbox, service *service_type,

 

char sender[MAX_GROUP_NAME], int max_groups,

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]