Advanced PHP Programming- P12

Chia sẻ: Thanh Cong | Ngày: | Loại File: PDF | Số trang:50

lượt xem

Advanced PHP Programming- P12

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Tham khảo tài liệu 'advanced php programming- p12', công nghệ thông tin, kỹ thuật lập trình phục vụ nhu cầu học tập, nghiên cứu và làm việc hiệu quả

Chủ đề:

Nội dung Text: Advanced PHP Programming- P12

  1. 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(): nrsrc_struct is the actual pointer you want the resource data to be stored in. nrsrc_struct_type is the type of struct the resource is (for example, FILE *). nzval_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); }
  2. 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 func- tion 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 over- ride 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: n Module startup n Module shutdown n Request startup n Request shutdown n 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 };
  3. 530 Chapter 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 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 reinstat- ed 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
  4. 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 connec- tion 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);
  5. 532 Chapter 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: n They provide global settings, independent of scripts. n They provide access controls on settings that can restrict developers from changing the INI settings in their scripts. n 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)
  6. Extension Basics 533 “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 exam- ple.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.
  7. 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 modi- fied. 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 sam- ple 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 func- tion, 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 *
  8. 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 dur- ing 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) {
  9. 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 };
  10. 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 dis- tributed 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 pro- vide 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,
  11. 538 Chapter 21 Extending PHP: Part I int *num_groups, char groups[][MAX_GROUP_NAME], int16 *mess_type, int *endian_mismatch, int max_mess_len, char *mess ); These functions provide the following: 1. Connecting to a spread daemon. 2. Disconnecting from a spread daemon. 3. Joining a group to listen on. 4. Sending a message to a single group. 5. Sending a message to multiple groups. 6. Receiving messages to a group you belong to. The strategy is to supply a PHP-level function for each of these C functions, except for SP_multicast() and SP_multigroup_multicast(), which PHP’s weak typing makes ideal to combine into a single function. Connections to spread will be handled via a resource. To start the PHP class, you generate a standard skeleton file using this: ext_skel --extname=spread The first step you need to take is to handle the resource management for the script. To do this, you need to create a static list identifier, le_pconn, and a destructor, close_spread_pconn(), which when handed a Spread connection resource will extract the spread connection inside and disconnect from it. Here’s how this looks: static int le_pconn; static void _close_spread_pconn(zend_rsrc_list_entry *rsrc) { mailbox *mbox = (int *)rsrc->ptr; if(mbox) { SP_disconnect(*mbox); free(mbox); } } mailbox is a type defined in the spread header files that is basically a connection identifier. MINIT During module initialization, you need to initialize the resource list le_pconn and declare constants.You are only interested in persistent connections, so you need to regis- ter only a persistent resource destructor, like this: PHP_MINIT_FUNCTION(spread) { le_pconn =
  12. An Example: The Spread Client Wrapper 539 zend_register_list_destructors_ex(NULL, _close_spread_pconn, “spread”, module_number); REGISTER_LONG_CONSTANT(“SP_LOW_PRIORITY”, LOW_PRIORITY, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT(“SP_MEDIUM_PRIORITY”, MEDIUM_PRIORITY, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT(“SP_HIGH_PRIORITY”, HIGH_PRIORITY, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT(“SP_UNRELIABLE_MESS”, UNRELIABLE_MESS, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT(“SP_RELIABLE_MESS”, RELIABLE_MESS, CONST_CS|CONST_PERSISTENT); /* ... more constants ... */ return SUCCESS; } Note The resource you are connecting to dictate whether you want persistent connections or not. In the case of Spread, a client connection causes a group event that must be propagated across all the Spread nodes. This is moderately expensive, so it makes sense to prefer persistent connections. MySQL, on the other hand, uses an extremely lightweight protocol in which connection establishment has a very low cost. In MySQL it makes sense to always use nonpersistent connections. Of course, nothing stops you as the extension author from providing both persistent and nonpersistent resources side-by-side if you choose. MSHUTDOWN The only resource you need in order to maintain this extension is the persistent resource list, which effectively manages itself.Thus, you don’t need to define an MSHUTDOWN hook at all. Module Functions To facilitate connecting to Spread, you need to write a helper function, connect(), that should take a spread daemon name (which is either a TCP address, such as, or a Unix domain socket, such as /tmp/NNNN) and a string, which is the private name (a name that is globally unique) of the connection. It should then either return an existing connection (from the persistent connection list indicated by le_pconn) or, if that is unsuccessful, create one. connect(), shown here, is forced to handle all the messiness of interacting with resources:
  13. 540 Chapter 21 Extending PHP: Part I int connect(char *spread_name, char *private_name) { mailbox *mbox; char private_group[MAX_GROUP_NAME]; char *hashed_details; int hashed_details_length; int rsrc_id; list_entry *le; hashed_details_length = sizeof(“spread_ _”) + strlen(spread_name) + strlen(private_name); hashed_details = (char *) emalloc(hashed_details_length); sprintf(hashed_details, “spread_%s_%s”, spread_name, private_name); /* look up spread connection in persistent_list */ if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_length, (void **) &le) == FAILURE) { list_entry new_le; int retval; mbox = (mailbox *) malloc(sizeof(int)); if ((retval = SP_connect(spread_name, private_name, 0, 0, mbox, private_group)) != ACCEPT_SESSION) { zend_error(E_WARNING, “Failed to connect to spread daemon %s, error returned was: %d”, spread_name, retval); efree(hashed_details); return 0; } new_le.type = le_pconn; new_le.ptr = mbox; if (zend_hash_update(&EG(persistent_list), hashed_details, hashed_details_length, (void *) &new_le, sizeof(list_entry), NULL) == FAILURE) { SP_disconnect(*mbox); free(mbox); efree(hashed_details); return 0; } } else { /* we have a pre-existing connection */ if (le->type != le_pconn) { // return badly free(mbox);
  14. An Example: The Spread Client Wrapper 541 efree(hashed_details); return 0; } mbox = (mailbox *)le->ptr; } rsrc_id = ZEND_REGISTER_RESOURCE(NULL, mbox, le_pconn); zend_list_addref(rsrc_id); efree(hashed_details); return rsrc_id; } Now you need to put these functions to work.The first function you need is the spread_connect() function to model SP_connect(). spread_connect() is a simple wrapper around connect(). It takes a spread daemon name and an optional private name. If a private name is not specified, a private name based on the process ID of the executing process is created and used. Here is the code for spread_connect(): PHP_FUNCTION(spread_connect) { char *spread_name = NULL; char *private_name = NULL; char *tmp = NULL; int spread_name_len; int private_name_len; int rsrc_id; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “s|s”, &spread_name, &spread_name_len, &private_name, &private_name_len) == FAILURE) { return; } if(!private_name) { tmp = (char *) emalloc(10); snprintf(tmp, MAX_PRIVATE_NAME,”php-%05d”, getpid()); private_name = tmp; } rsrc_id = connect(spread_name, private_name); if(tmp) { efree(tmp); } RETURN_RESOURCE(rsrc_id); } Now that you can make a connection, you also need to be able to disconnect.You can bootstrap the spread_disconnect() function off the resource destructor infrastructure to make its implementation extremely simple. Instead of having to actually fetch the Spread connection’s mailbox from the resource and close it using SP_disconnect(), you
  15. 542 Chapter 21 Extending PHP: Part I can simply delete the resource from the resource list.This invokes the registered destruc- tor for the resource, which itself calls SP_disconnect(). Here is the code for spread_disconnect(): PHP_FUNCTION(spread_disconnect) { zval **spread_conn; mailbox *mbox; int id = -1; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “r”, &spread_conn) == FAILURE) { return; } zend_list_delete(Z_RESVAL_PP(spread_conn)); RETURN_TRUE; } As a Spread client, you need to belong to a group to be able to receive messages for the group. Creating a group is as simple as joining it with SP_join(); if it is nonexistent, it will be implicitly created.The spread_join() function will affect this, with one minor twist:You want to able to join multiple groups by passing an array.To accomplish this, you can accept the second parameter as a raw zval and switch on its type in the code. If you are passed an array, you will iterate through it and join each group; otherwise, you will convert the scalar to a string and attempt to join that. Notice that because you are doing conversion on the zval, you need to separate it by using SEPARATE_ZVAL(). Here is the code for the spread_join function: PHP_FUNCTION(spread_join) { zval **group, **mbox_zval; int *mbox, sperrno; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “rz”, mbox_zval, group) == FAILURE) { return; } ZEND_FETCH_RESOURCE(mbox, int *, mbox_zval, -1, “Spread-FD”, le_conn); SEPARATE_ZVAL(group); if(Z_TYPE_PP(group) == IS_ARRAY) { char groupnames[100][MAX_GROUP_NAME]; zval *tmparr, **tmp; int n = 0; int error = 0; zend_hash_internal_pointer_reset(Z_ARRVAL_PP(group)); while(zend_hash_get_current_data(Z_ARRVAL_PP(group), (void **) &tmp) == SUCCESS && n < 100) { convert_to_string_ex(tmp); if( (sperrno = SP_join(*mbox, Z_STRVAL_PP(tmp)) < 0) {
  16. An Example: The Spread Client Wrapper 543 zend_error(E_WARNING, “SP_join error(%d)”, sperrno); error = sperrno; } n++; zend_hash_move_forward(Z_ARRVAL_PP(group)); } if (error) { RETURN_LONG(error); } } else { convert_to_string_ex(group); if( (sperrno = SP_join(*mbox, Z_STRVAL_PP(group))) < 0) { zend_error(E_WARNING, “SP_join error(%d)”, sperrno); RETURN_LONG(sperrno); } } RETURN_LONG(0); } To receive data in Spread, you simply call SP_receive() on the Spread mailbox.When SP_receive() returns, it contains not only a message but metadata on who sent the message (the sender’s private name), the groups it was sent to, and the type of message. The spread_receive() function should return the following as an associative array: array( message => ‘Message’, groups => array( ‘groupA’, ‘groupB’), message_type => RELIABLE_MESS, sender => ‘spread_12345’); spread_receive() is pretty straightforward. Note the looping you need to do in SP_receive() to handle BUFFER_TOO_SHORT errors and note the assemblage of return_value: PHP_FUNCTION(spread_receive) { zval **mbox_zval, *groups_zval; int *mbox; int sperrno; int i, endmis, ret, ngrps, msize; int16 mtype; service stype; static int oldmsize = 0; static int oldgsize = 0; static int newmsize = (1
  17. 544 Chapter 21 Extending PHP: Part I if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “r”, mbox_zval) == FAILURE) { return; } ZEND_FETCH_RESOURCE(mbox, int *, mbox_zval, NULL, “Spread-FD”, le_pconn); try_again: { if(oldgsize != newgsize) { if(groups) { groups = (char*) erealloc(groups, newgsize*MAX_GROUP_NAME); } else { groups = (char*) emalloc(newgsize*MAX_GROUP_NAME); } oldgsize=newgsize; } if(oldmsize != newmsize) { if(mess) { mess = (char *) erealloc(mess, newmsize); } else { mess = (char *) emalloc(newmsize); } oldmsize = newmsize; } if((ret=SP_receive(*mbox, &stype, sender, newgsize, &ngrps, groups, &mtype, &endmis, newmsize, mess))
  18. An Example: The Spread Client Wrapper 545 add_assoc_stringl(return_value, “sender”, sender, strlen(sender), 1); return; } Finally, you need to handle sending messages. As noted earlier, Spread actually has two functions for this: SP_multicast(), which allows for sending messages to a single group, and SP_multigroup_multicast(), which sends to multiple groups.The latter cannot be implemented in terms of the former because it would break the ordering semantics of the message (because it would be possible for another client to interject a message in between the transmission to the two groups). Here is the code for spread_multicast(): PHP_FUNCTION(spread_multicast) { zval **group = NULL; zval **mbox_zval = NULL; char *message; int *mbox, service_type, mess_type, sperrno, message_length; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC4, “rlzls”, mbox_zval, service_type, group, mess_type, &message, &message_length) == FAILURE) { return; } SEPARATE_ZVAL(group) ZEND_FETCH_RESOURCE(mbox, int *, mbox_zval, -1, “Spread-FD”, le_conn); if(Z_TYPE_PP(group) == IS_ARRAY) { char groupnames[100][MAX_GROUP_NAME]; zval *tmparr, **tmp; int n = 0; zend_hash_internal_pointer_reset(Z_ARRVAL_PP(group)); while(zend_hash_get_current_data(Z_ARRVAL_PP(group), (void **) &tmp) == SUCCESS && n < 100) { convert_to_string_ex(tmp); memcpy(groupnames[n], Z_STRVAL_PP(tmp), MAX_GROUP_NAME); n++; zend_hash_move_forward (Z_ARRVAL_PP(group)); } if((sperrno = SP_multigroup_multicast(*mbox, service_type, n, (const char (*)[MAX_GROUP_NAME]) groupnames, mess_type, message_length, message))
  19. 546 Chapter 21 Extending PHP: Part I if (sperrno = (SP_multicast(*mbox, service_type, Z_STRVAL_PP(group), mess_type, message_length, message))
  20. Further Reading 547 Using the Spread Module After compiling and installing the Spread module by following the steps outlined at the beginning of the chapter, you are ready to use it. Here is a logging class that allows you to send arbitrary message to a spread group: The Spread_Logger class connects to Spread in its constructor, and send() wraps spread_multicast(). Here is a sample usage of the class, which connects to a local spread daemon and sends a test message to the test group: Further Reading Some documentation on PHP extension authoring is available in the online PHP docu- mentation, at A statement about the dili- gence put into maintaining that section of the documentation is at the section head “Those who know don’t talk.Those who talk don’t know.”This chapter aims to have disproved that statement.
Đồng bộ tài khoản