Tải bản đầy đủ (.pdf) (50 trang)

Advanced PHP Programming- P12

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (523.88 KB, 50 trang )

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);
}
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
529
Extension Basics
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
};
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
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:
n
CONST_CS
—Constant is case-sensitive.
n

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
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
531
Extension Basics
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
struct
s 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);
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
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)
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
533
Extension Basics

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.
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
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 *
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
535
Extension Basics
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) {
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
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
};
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
537
An Example: The Spread Client Wrapper
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,
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
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 =
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
539
An Example: The Spread Client Wrapper
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
10.0.0.1:NNNN, 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:
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
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);
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
541
An Example: The Spread Client Wrapper
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
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
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) {
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
543
An Example: The Spread Client Wrapper
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<<15);
static int newgsize = (1<<6);
static char* groups=NULL;
static char* mess=NULL;
char sender[MAX_GROUP_NAME];
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
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))<0) {
if(ret==BUFFER_TOO_SHORT) {
newmsize=-endmis;
newmsize++;
msize = oldmsize;
goto try_again;
}
}
msize = oldmsize;
}
/* spread does not null terminate these, so we should */
mess[msize + 1] =

\0


;
/* we

ve got the answer; let

s wind up our response */
array_init(return_value);
add_assoc_stringl(return_value,

message

, mess, msize, 1);
MAKE_STD_ZVAL(groups_zval);
array_init(groups_zval);
for(i = 0; i < ngrps; i++) {
add_index_stringl(groups_zval, i, &groups[i*MAX_GROUP_NAME],
strlen(&groups[i*MAX_GROUP_NAME]), 1);
}
add_assoc_zval(return_value,

groups

, groups_zval);
add_assoc_long(return_value,

message_type

, mtype);
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
545

An Example: The Spread Client Wrapper
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)) <0)
{
zend_error(E_WARNING,

SP_multicast error(%d)


, sperrno);
RETURN_FALSE;
}
}
else {
convert_to_string_ex(group);
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
546
Chapter 21 Extending PHP: Part I
if (sperrno = (SP_multicast(*mbox, service_type,
Z_STRVAL_PP(group), mess_type,
message_length, message)) <0)
{
zend_error(E_WARNING,

SP_mulicast error(%d)

, sperrno);
RETURN_FALSE;
}
}
RETURN_TRUE;
}
Note
It’s worth noting that as a Spread client, you do not need to join groups to send messages—only to receive
them. When you join a group, Spread needs to buffer all the messages you have not yet received, so if you
do not need to incur this work, you should not.
Now all you need to do is finish registering the functions, and then you are all set. First
you define the function table:

function_entry spread_functions[] = {
PHP_FE(spread_connect, NULL)
PHP_FE(spread_multicast, NULL)
PHP_FE(spread_disconnect, NULL)
PHP_FE(spread_join, NULL)
PHP_FE(spread_receive, NULL)
{NULL, NULL, NULL}
};
Then you register the module:
zend_module_entry spread_module_entry = {
STANDARD_MODULE_HEADER,

spread

,
spread_functions,
PHP_MINIT(spread),
NULL,
NULL,
NULL,
PHP_MINFO(spread),

1.0

,
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_SPREAD
ZEND_GET_MODULE(spread)
#endif

Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
547
Further Reading
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:
<?php
if(!extension_loaded(

spread

)) {
dl(

spread.so

);
}
class Spread_Logger {
public $daemon;
public $group;
private $conn;
public function _ _construct($daemon, $group)
{
$this->daemon = $daemon;
$this->group = $group;
$this->conn = spread_connect($daemon);

}
public function send($message) {
return spread_multicast($this->conn, 0, $this->group,
SP_REGULAR_MESS, $message);
}
}
?>
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:
<?php
$spread = new Spread_Logger(

127.0.0.1:4803

,

test

);
$spread->send(

This is a test message.


);
?>
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.
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×