15.2. Quickstart
Instead of slowly explaining some of the building blocks of the scripting engine, this section dives into coding an extension, so do not worry if you don't see the whole picture right away.
Imagine you are writing a web site but need a function, which will repeat a string n times. Writing this in PHP is simple:
function self_concat($string, $n)
{
$result = "";
for ($i = 0; $i < $n; $i++) {
$result .= $string;
}
return $result;
}
self_concat("One", 3) returns "OneOneOne". self_concat("One", 1) returns "One".
Imagine that for some odd reason, you need to call this function often, with very long strings and large values of n. This means that you'd have a huge amount of concatenation and memory reallocation going on in your script, which could significantly slow things down. It would be much faster to have a function that allocates a large enough string to hold the resulting string and then repeat $string n times, not needing to reallocate memory every loop iteration.
The first step in creating an extension for your function is to write the function definition file for the functions you want your extension to have. In this case, the file will have only one line with the prototype of the function self_concat():
string self_concat(string str, int n)
The general format of the function definition file is one function per line. You can specify optional parameters and a wide variety of PHP types, including bool, float, int, array, and others.
Save the file as myfunctions.def in the ext/ directory under the PHP's source tree.
Now it's time to run it through the extension skeleton creator. The script is called ext_skel and sits in the ext/ directory of the PHP source tree (more information can be found in the README.EXT_SKEL file under the main PHP source directory). Assuming you saved your function definitions in a file called myfunctions.def and you would like the extension to be called myfunctions, you would use the following line to create your skeleton extension:
./ext_skel --extname=myfunctions --proto=myfunctions.def
This creates a directory myfunctions/ under the ext/ directory. First thing you'd probably want to do is get the skeleton to compile so that you're ready for actually writing and testing your C code. There are two ways to compile the extension:
This chapter uses the second method because it's slightly easier to begin with. If you're interested in building your extension as a loadable module, you should read the README.SELF-CONTAINED_EXTENSIONS file in the PHP source tree's root directory. To get the extension to compile, you need to edit its config.m4 file, which can be found in ext/myfunctions/. As your extension does not wrap any external C libraries, you will want to add support of the --enable-myfunctions configure switch to PHP's build system (the with-extension switch is used for extensions that need to allow the user to specify a path to the relevant C library). You can enable the switch by uncommenting the following two auto-generated lines:
PHP_ARG_ENABLE(myfunctions, whether to enable myfunctions support,
[ --enable-myfunctions Include myfunctions support])
Now all that's left to do is to run ./buildconf in the root of the PHP source tree, which will create a new configure script. You can check that your new configure option made it into configure by finding it in the output of ./configure --help. Now, reconfigure PHP with all of your favorite switches and include the --enable-myfunctions switch. Last but not least, rebuild PHP by running make.
ext_skel should have added two PHP functions to your skeleton extension: self_concat() which is the function you want to implement, and confirm_myfunctions_compiled(), which can be called to check that you properly enabled the myfunctions extension in your build of PHP. After you finish developing your PHP extension, remove the latter function.
<?php
print confirm_myfunctions_compiled("myextension");
?>
Running this script would result in something similar to the following being printed:
"Congratulations! You have successfully modified ext/myfunctions
config.m4. Module myfunctions is now compiled into PHP."
In addition, the ext_skel script creates a myfunctions.php script that you can also run to verify that your extension was successfully built into PHP. It shows you a list of functions that your extension supports.
Now that you've managed to build PHP with your extension, it's time to actually start hacking at the self_concat() function.
The following is the skeleton that the ext_skel script created:
/* {{{ proto string self_concat(string str, int n)
*/
PHP_FUNCTION(self_concat)
}
char *str = NULL;
int argc = ZEND_NUM_ARGS();
int str_len;
long n;
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
return;
php_error(E_WARNING, "self_concat: not yet implemented");
}
/* }}} */
The auto-generated PHP function includes comments around the function declaration which are used for self-documentation and code-folding in editors such as vi and Emacs. The function itself is defined by using the PHP_FUNCTION() macro, which creates a function prototype suitable for the Zend Engine. The logic itself is divided into semantic parts, the first where you retrieve your function arguments and the latter the logic itself.
To retrieve the parameters passed to your function, you'll want to use the zend_parse_parameters() API function which has the following prototype:
zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, ...);
The first argument is the number of arguments that were passed to your function. You will usually pass it ZEND_NUM_ARGS(), which is a macro that equals the amount of parameters passed to your PHP function. The second argument is for thread-safety purposes, and you should always pass it the TSRMLS_CC macro, which is explained later. The third argument is a string specifying what types of parameters you are expecting, followed by a list of variables that should be updated with the parameters' values. Because of PHP's loose and dynamic typing, when it makes sense, the parameters will convert to the requested types if they are different. For example, if the user sends an integer and you request a floating-point number, zend_parse_parameters() automatically converts the integer to the corresponding floating-point number. If the actual value cannot be converted to the expected type (for example, integer to array), a warning is triggered.
Table 15.1 lists types you can specify. For completeness, some types that we haven't discussed yet are included.
Table 15.1. Type SpecifiersType Specifier | Corresponding C Type | Description |
---|
l | long | Signed integer. | d | double | Floating-point number. | s | char *, int | Binary string including length. | b | zend_bool | Boolean value (1 or 0). | r | zval * | Resource (file pointer, database connection, and so on). | a | zval * | Associative array. | o | zval * | Object of any type. | O | zval * | Object of a specific type. This requires you to also pass the class type you want to retrieve. | z | zval * | The zval without any manipulation. |
To understand the last few options, you need to know that a zval is the Zend Engine's value container. Whether the value is a Boolean, a string, or any other type, its information is contained in the zval union. We will not access zval's directly in this chapter, except through some accessor macros, but the following is more or less what a zval value looks like in C, so that you can get a better idea of what's going on:
typedef union _zval {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
} zval;
In our examples, we use zend_parse_parameters() with basic types, receiving their values as native C types and not as zval containers.
For zend_parse_parameters() to be able to change the arguments that are supposed to return the function parameters, you need to send them by reference. Take a closer look at self_concat():
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
return;
Notice that the generated code checks for the return value FAILURE (SUCCESS in case of success) to see if the function has succeeded. If not, it just returns because, as previously mentioned, zend_parse_parameters() takes care of triggering warnings. Because your function wants to retrieve a string str and an integer n, it specifies "sl" as its type specifier string. s requires two arguments, so we send references to both a char * and an int (str and str_len) to the zend_parse_parameters() function. Whenever possible, always use the string's length str_len in your source code to make sure your functions are binary safe. Don't use functions such as strlen() and strcpy() unless you don't mind if your functions don't work for binary string. Binary strings are strings that can contain nulls. Binary formats include image files, compressed files, executable files, and more. "l" just requires one argument, so we pass it the reference of n. Although for clarity's sake, the skeleton script creates C variable names that are identical to the argument names in your specified function prototype; there's no need to do so, although it is recommended practice.
Back to conversion rules. All the three following calls to self_concat() result in the same values being stored in str, str_len, and n:
self_concat("321", 5);
self_concat(321, "5");
self_concat("321", "5");
str points to the string "321", str_len equals 3, and n equals 5.
Before we write the code that creates the concatenated string and returns it to PHP, we need to cover two important issues: memory management and the API for returning values from internal PHP functions.
15.2.1. Memory Management
PHP's API for allocating memory from the heap is almost identical to the standard C API. When writing extensions, use the following API functions that correspond to their C counterparts (and therefore are not explained):
emalloc(size_t size);
efree(void *ptr);
ecalloc(size_t nmemb, size_t size);
erealloc(void *ptr, size_t size);
estrdup(const char *s);
estrndup(const char *s, unsigned int length);
At this point, any experienced C developer should be thinking something like, "What? strndup() doesn't exist in standard C?" Well, that is correct because it is a GNU extension typically available on Linux. estrndup() is the only function that is special to PHP. It behaves like estrdup(), but you can specify the length of the string you want to duplicate (without the terminating null) and is, therefore, binary safe. This is recommended over estrdup().
Under almost all circumstances, you should use these allocation functions. There are some cases where extensions need to create memory that will be persistent in between requests where regular malloc() has to be used, but unless you know what you are doing, you should always use these functions. PHP will crash if you return values into the scripting engine that are not allocated with these functions, but with their standard C counterparts.
Advantages of these functions are that any such allocated memory that is accidentally not freed will be released at the end of a request. Therefore, it can't cause real memory leaks. However, don't rely on this, and make sure you free memory when you are supposed toboth for debugging and performance reasons. Other advantages include improved performance in multi-threaded environments, detection of memory corruption in debug mode, and more.
Another important point to mention is that you don't have to check the return values of the memory allocation functions for null. When memory allocation fails, they will bail out with an E_ERROR and will, therefore, never return.
15.2.2. Returning Values from PHP Functions
The extension API includes a rich collection of macros that allows you to return values from your functions. These macros come in two main flavors. The first is of the form RETVAL_type(), which sets the return value but your C code keeps on executing. This is usually used if you still want to do some cleaning up before returning control over to the scripting engine. You will then need to use the C return statement "return;" to return to PHP. The latter, which are the more popular macros, are of the form RETURN_type(), which set the return type and return control back to PHP. Table 15.2 explains most of the existing macros.
Table 15.2. Return Values MacrosSetting the Return Value and Ending the Function | Setting the Return Value | Macro Return Type and Parameters |
---|
RETURN_LONG(l) | RETVAL_LONG(l) | Integer. | RETURN_BOOL(b) | RETVAL_BOOL(b) | Boolean (1 or 0). | RETURN_NULL() | RETVAL_NULL() | Null. | RETURN_DOUBLE(d) | RETVAL_DOUBLE(d) | Floating point. | RETURN_STRING(s, dup) | RETVAL_STRING(s, dup) | String. If dup is 1, the engine will duplicate s using estrdup() and will use the copy. If dup is 0, it will use s. | RETURN_STRINGL(s, l, dup) | RETVAL_STRINGL(s, l, dup) | String value of length l. Same as the previous entry, but faster when duplicating because the length of s is specified by you in the macro. | RETURN_TRUE | RETVAL_TRUE | Returns the Boolean value true. Note that this macro doesn't have braces. | RETURN_FALSE | RETVAL_FALSE | Returns the Boolean value TRue. Note that this macro doesn't have braces. | RETURN_RESOURCE(r) | RETVAL_RESOURCE(r) | Resource handle. |
15.2.3. Completing self_concat()
Now that you have learned how to allocate memory and return values from PHP extension functions, we can complete the code for self_concat():
/* {{{ proto string self_concat(string str, int n)
*/
PHP_FUNCTION(self_concat)
}
char *str = NULL;
int argc = ZEND_NUM_ARGS();
int str_len;
long n;
char *result; /* Points to resulting string */
char *ptr; /* Points at the next location we want to copy to */
int result_length; /* Length of resulting string */
if (zend_parse_parameters(argc TSRMLS_CC, "sl", &str, &str_len, &n) == FAILURE)
return;
/* Calculate length of result */
result_length = (str_len * n);
/* Allocate memory for result */
result = (char *) emalloc(result_length + 1);
/* Point at the beginning of the result */
ptr = result;
while (n--) {
/* Copy str to the result */
memcpy(ptr, str, str_len);
/* Increment ptr to point at the next position we want to write to */
ptr += str_len;
}
/* Null terminate the result. Always null-terminate your strings even if they are
binary strings */
*ptr = '\0';
/* Return result to the scripting engine without duplicating it */
RETURN_STRINGL(result, result_length, 0);
}
/* }}} */
All you need to do now is to recompile PHP, and you've written your first PHP function.
Let's check and see if it really works. Run the following script in your freshly compiled PHP tree:
<?php
for ($i = 1; $i <= 3; $i++) {
print self_concat("ThisIsUseless", $i);
print "\n";
}
?>
You should get the following result:
ThisIsUseless
ThisIsUselessThisIsUseless
ThisIsUselessThisIsUselessThisIsUseless
15.2.4. Summary of Example
You have learned how to write a simple PHP function. Going back to the beginning of this chapter, we mentioned two main motivations for writing PHP functionality in C. The first was to write some of your algorithms in C for performance or for functionality reasons. The previous example should allow you to quickly get started with these kind of extensions. The second motivation was for wrapping third-party libraries. We will discuss this next.
15.2.5. Wrapping Third-Party Extensions
In this section, you learn how to write a more useful and complete extension. It wraps a C library and explains how to write an extension with various PHP functions that work together.
15.2.5.1 Motivation
Probably the most common PHP extension is one which wraps a third party C library. This may include database server libraries, such as MySQL or Oracle, XML technology libraries, such as libxml2 or expat, graphics manipulation libraries, such as ImageMagick or GD, and lots more.
In this section, we write such an extension from scratch, yet again using the script for creating skeleton extensions, which saves us much work. This extension wraps the standard C functions fopen(), fclose(), fread(), fwrite(), and feof().
The extension uses an abstract datatype called resource to represent the opened file FILE *. You will notice that most PHP extensions that deal with datatypes, such as database connections and file handles, use resources because the engine itself can't "understand" them directly.
The list of C APIs we want to implement in our PHP extension include
FILE *fopen(const char *path, const char *mode);
int fclose(FILE *stream);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
int feof(FILE *stream);
We implement these functions in a way that fits the PHP spirit both in naming conventions and simplicity of the API. If you ever contribute your code to the PHP community, you will be expected to follow the agreed-upon conventions and not necessarily follow the C library's API, as is. Some of the conventions, but not all, are documented in the CODING_STANDARDS file in the PHP source tree. That being said, this functionality has already been present in PHP from its early days with an API similar to the C library's API. Your PHP installation already supports fopen(), fclose(), and more PHP functions.
So, here's what our PHP spirited API would look like:
resource file_open(string filename, string mode)
file_open() accepts two strings (filename and mode) and returns a resource handle to the file.
bool file_close(resource filehandle)
file_close() receives a resource handle and returns true/false if the operation succeeded.
string file_read(resource filehandle, int size)
file_read() receives a resource handle and the amount of bytes to read. It returns the
read string.
bool file_write(resource filehandle, string buffer)
file_write() receives a resource handle and the string to write. It returns true/false if
the operation succeeded.
bool file_eof(resource filehandle)
file_eof() receives a resource handle and returns true/false if end of-file has been reached.
Therefore, our function definition file, which we'll save in the ext/ directory as myfile.def will look as follows:
resource file_open(string filename, string mode)
bool file_close(resource filehandle)
string file_read(resource filehandle, int size)
bool file_write(resource filehandle, string buffer)
bool file_eof(resource filehandle)
Next, run it through the ext_skel script with the following command inside the ext/ directory of the source tree:
./ext_skel --extname=myfile --proto=myfile.def
Then, follow the instructions from the previous example on how to build your newly created extension. You will receive some compile errors on lines that include the FETCH_RESOURCE() macro, which the skeleton script can't complete on its own. To get your skeleton extension to build, you can just comment them out for now.
15.2.5.2 Resources
A resource is an abstract value that can hold any kind of information. As previously mentioned, this information often consists of data such as file handles, database connection structures, and other complex types.
The main reason for using resources is that they are managed via a centralized list that automatically destroys the resource in case the PHP developer hasn't done so explicitly in his script.
For instance, consider writing a script that opens a MySQL connection via the call mysql_connect(), but doesn't call mysql_close() to close it once the database connection resource isn't in use anymore. In PHP, the resource mechanism detects when this resource should be destroyed, and will destroy it (at the latest) at the end of the current request and often much earlier. This gives a bulletproof mechanism for eliminating the possibility for resource leaks. Without such a mechanism, after a few web requests, the web server could be potentially leaking a lot of resources, which could lead to server crashes or malfunction.
15.2.5.3 Registering Resources Types
How do you use resources?
The Zend Engine has made it relatively easy to work with resources. The first thing you have to do is register your resource type with the engine.
The API function to use is
int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char
*type_name, int module_number)
The function returns a resource type id, which should be saved by the extension in a global variable and will be passed to other resource API calls when necessary. ld, the destructor function, should be called for this resource. pld is used for persistent resources that can survive in between requests and won't be covered in this chapter. type_name is a string with a descriptive name for the type. module_number is used internally by the engine, and when we call this function, we will just pass through an already defined module_number variable.
Back to our example: We will add the following code to our myfile.c source file. It includes the definition for the destructor function that is passed to the zend_register_list_destructors_ex() registration function (it should be added early in the file so that it's defined by the time you make the zend_register_list_destructors_ex() call):
static void myfile_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
FILE *fp = (FILE *) rsrc->ptr;
fclose(fp);
}
After adding the registration line to your auto-generated PHP_MINIT_FUNCTION() function, it should look similar to the following:
PHP_MINIT_FUNCTION(myfile)
{
/* If you have INI entries, uncomment these lines
ZEND_INIT_MODULE_GLOBALS(myfile, php_myfile_init_globals, NULL);
REGISTER_INI_ENTRIES();
*/
le_myfile = zend_register_list_destructors_ex(myfile_dtor, NULL,"standard-c-file",
module_number);
return SUCCESS;
}
* Note that le_myfile is a global variable that is already defined by the ext_skel script.
PHP_MINIT_FUNCTION() is the per-module (extension) startup function that is part of the API exposed to your extension. Table 15.3 gives you a short overview of the available functions and how you can use them.
Table 15.3. Function Declaration MacrosFunction Declaration Macro | Semantics |
---|
PHP_MINIT_FUNCTION() | The module startup function is called by the engine when PHP loads and allows it to do necessary one-time initializations, such as registering resource types, registering INI values, and more. | PHP_MSHUTDOWN_FUNCTION() | The module shutdown function is called by the engine when PHP shuts down completely and is usually used for unregistering INI entries. | PHP_RINIT_FUNCTION() | The per-request startup function is called at the beginning of each request served by PHP, and it is used to manage per-request logic. | PHP_RSHUTDOWN_FUNCTION() | The per-request shutdown function is called at the end of each request served by PHP, and it is most often used to clean up the per-request startup function's logic. | PHP_MINFO_FUNCTION() | The module info function is called during the PHP phpinfo() function and prints out this modules information. |
15.2.5.4 Creating and Registering New Resources
We are about to implement the file_open() function. After we open the file and receive a FILE *, we need to register it with the resource mechanism. The main macro to achieve this is
ZEND_REGISTER_RESOURCE(rsrc_result, rsrc_pointer, rsrc_type);
See Table 15.4 for an explanation of the macro's arguments.
Table 15.4. ZEND_REGISTER_RESOURCE Macro ArgumentsMacro Argument | Parameter Type |
---|
rsrc_result | zval *, which should be set with the registered resource information. | rsrc_pointer | Pointer to our resource data. | rsrc_type | The resource id obtained when registering the resource type. |
15.2.5.5 File Functions
Now that you know how to use the ZEND_REGISTER_ RESOURCE() macro, you're almost ready to write file_open(). There's only one more subject we need to cover.
As PHP also runs under multi-threaded servers, you cannot use the standard C file access functions. This is because a running PHP script in one thread might change the current working directory, thus leading an fopen() call using a relative path in another thread failing to open the intended file. To prevent such problems, the PHP framework provides VCWD (virtual current working directory) macros that should be used instead of any file access functions that rely on the current working directory. (Table 15.5 lists the available macros.) The macros behave the same as the functions they replace, and everything is handled for you transparently. Standard C library functions that are not available on certain platforms are, therefore, not supported by the VCWD framework. For example, chown(), which doesn't exist on Win32, won't have a corresponding VCWD_CHOWN() macro defined.
Table 15.5. List of VCWD MacrosStandard C Library | VCWD Macro | Comment |
---|
getcwd() | VCWD_GETCWD() | | fopen() | VCWD_FOPEN() | | open() | VCWD_OPEN() | Used for the two-parameter version. | open() | VCWD_OPEN_MODE() | Used for the three-parameter version of open(). | creat() | VCWD_CREAT() | | chdir() | VCWD_CHDIR() | | getwd() | VCWD_GETWD() | | realpath() | VCWD_REALPATH() | | rename() | VCWD_RENAME() | | stat() | VCWD_STAT() | | lstat() | VCWD_LSTAT() | | unlink() | VCWD_UNLINK() | | mkdir() | VCWD_MKDIR() | | rmdir() | VCWD_RMDIR() | | opendir() | VCWD_OPENDIR() | | popen() | VCWD_POPEN() | | access() | VCWD_ACCESS() | | utime() | VCWD_UTIME() | | chmod() | VCWD_CHMOD() | | chown() | VCWD_CHOWN() | |
15.2.5.6 Writing Your First Resource-Enabled PHP Function
Implementing file_open() should now be easy, and it should look as follows:
PHP_FUNCTION(file_open)
{
char *filename = NULL;
char *mode = NULL;
int argc = ZEND_NUM_ARGS();
int filename_len;
int mode_len;
FILE *fp;
if (zend_parse_parameters(argc TSRMLS_CC, "ss", &filename, &filename_len, &mode,
&mode_len) == FAILURE) {
return;
}
fp = VCWD_FOPEN(filename, mode);
if (fp == NULL) {
RETURN_FALSE;
}
ZEND_REGISTER_RESOURCE(return_value, fp, le_myfile);
}
You might notice that the first argument to the resource registration macro is a variable called return_value, which has appeared out of nowhere. This variable is automatically defined by the extension framework and is a zval * to the function's return value. The previously discussed macros, which affect the return value such as RETURN_LONG() and RETVAL_BOOL(), actually change the value of return_value. Therefore, it is easy to guess that the code registers our acquired file pointer fp and sets the return_value to the registered resource.
15.2.5.7 Accessing a Resource
To access a resource, you need to use the following macro (see Table 15.6 for an explanation of its arguments):
ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id, default_id, resource_type_name,
resource_type);
Table 15.6. ZEND_FETCH_RESOURCE Macro ArgumentsParameter | Meaning |
---|
rsrc | Variable that is assigned the resource value. It has to be of the same type as the resource. | rsrc_type | Type of rsrc that will be used to cast the resource internally to the correct type. | passed_id | The resource value to look for (as a zval **). | default_id | If this value is not 1, this id is taken. It is used for implementing a default for the resource. | resource_type_name | A short type name for your resource which is used in error messages. | resource_type | The resource type id of the registered resource. |
Using this macro, we can now implement file_eof():
PHP_FUNCTION(file_eof)
{
int argc = ZEND_NUM_ARGS();
zval *filehandle = NULL;
FILE *fp;
if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) ==FAILURE) {
return;
}
ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-c file",le_myfile);
if (fp == NULL) {
RETURN_FALSE;
}
if (feof(fp) <= 0) {
/* Return eof also if there was an error */
RETURN_TRUE;
}
RETURN_FALSE;
}
15.2.5.8 Removing a Resource
To remove a resource, you usually want to use the following macro:
int zend_list_delete(int id)
The macro is passed the id of the resource, and returns either SUCCESS or FAILURE. If the resource exists, prior to removing it from the Zend resource list, it will call the registered destructor for the resource type. Therefore, in our example, you don't have to obtain the file pointer and fclose() it before removing the resource, but you can just go ahead and delete it.
Using this macro, we can now implement file_close():
PHP_FUNCTION(file_close)
{
int argc = ZEND_NUM_ARGS();
zval *filehandle = NULL;
if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) == FAILURE) {
return;
}
if (zend_list_delete(Z_RESVAL_P(filehandle)) == FAILURE) {
RETURN_FALSE;
}
RETURN_TRUE;
}
You must be asking yourself what Z_RESVAL_P() does. When we retrieve the resource from the argument list using zend_parse_parameters(), we receive it in the form of a zval. To access the resource id, we use the Z_RESVAL_P() macro, and then pass it to zend_list_delete().
A whole family of macros aid in accessing values stored in zval values (see Table 15.7 for a list of macros). Although zend_parse_parameters() in most cases returns the values as the corresponding C type, you might want to deal with a zval directly, including in the case of resources.
Table 15.7. zval Accessor MacrosMacros | Used to Access | C Type |
---|
Z_LVAL, Z_LVAL_P, Z_LVAL_PP | Integer value | Long | Z_BVAL, Z_BVAL_P, Z_BVAL_PP | Boolean value | zend_bool | Z_DVAL, Z_DVAL_P, Z_DVAL_PP | Floating-point value | double | Z_STRVAL, Z_STRVAL_P, Z_STRVAL_PP | String value | char * | Z_STRLEN, Z_STRLEN_P, Z_STRLEN_PP | String length | int | Z_RESVAL, Z_RESVAL_P, Z_RESVAL_PP | Resource value | Long | Z_ARRVAL, Z_ARRVAL_P, Z_ARRVAL_PP | Associative array | HashTable * | Z_TYPE, Z_TYPE_P, Z_TYPE_PP | The zval's type | Enumeration (IS_NULL, IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRAY, IS_OBJECT, IS_BOOL, IS_RESOURCE) | Z_OBJPROP, Z_OBJPROP_P, Z_OBJPROP_PP | The object's properties hash (won't be covered in this chapter). | HashTable * | Z_OBJCE, Z_OBJCE_P, Z_OBJCE_PP | The object's class information (won't be covered in this chapter). | zend_class_entry |
15.2.5.9 Macros Used to Access zval Values
All macros have three forms: one that accepts zvals, another one for zval *s, and finally one for zval **s. The difference in their names is that the first has no suffix, the zval * has a suffix of _P (as in one pointer), and the latter, zval **, has a suffix of _PP (two pointers).
Now, you have enough information to complete the file_read() and file_write() functions on your own. Here's a possible implementation:
PHP_FUNCTION(file_read)
{
int argc = ZEND_NUM_ARGS();
long size;
zval *filehandle = NULL;
FILE *fp;
char *result;
size_t bytes_read;
if (zend_parse_parameters(argc TSRMLS_CC, "rl", &filehandle, &size) == FAILURE) {
return;
}
ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-c file", le_myfile);
result = (char *) emalloc(size+1);
bytes_read = fread(result, 1, size, fp);
result[bytes_read] = '\0';
RETURN_STRING(result, 0);
}
PHP_FUNCTION(file_write)
{
char *buffer = NULL;
int argc = ZEND_NUM_ARGS();
int buffer_len;
zval *filehandle = NULL;
FILE *fp;
if (zend_parse_parameters(argc TSRMLS_CC, "rs", &filehandle, &buffer, &buffer_len) ==
FAILURE) {
return;
}
ZEND_FETCH_RESOURCE(fp, FILE *, &filehandle, -1, "standard-c file", le_myfile);
if (fwrite(buffer, 1, buffer_len, fp) != buffer_len) {
RETURN_FALSE;
}
RETURN_TRUE;
}
15.2.5.10 Testing the Extension
You are now ready to write a test script to check that the extension works. Here's a sample script that opens a file test.txt, prints its contents to the standard output, and creates a copy of the file as test.txt.new:
<?php
$fp_in = file_open("test.txt", "r") or die("Unable to open input file\n");
$fp_out = file_open("test.txt.new", "w") or die("Unable to open output file\n");
while (!file_eof($fp_in)) {
$str = file_read($fp_in, 1024);
print($str);
file_write($fp_out, $str);
}
file_close($fp_in);
file_close($fp_out);
?>
15.2.6. Global Variables
You might want to use global C variables in your extension, either for your own internal use or for receiving php.ini values of your extension's registered INI directives (INI is discussed in the next section). As PHP is designed to run in multi-threaded environments, you shouldn't define global variables on your own. PHP supplies a mechanism that creates global variables for you, which can be used both in threaded and non-threaded environments. You should always use this mechanism and not define your own global variables. These global variables are then accessed via a macro and used just as if they are regular global variables.
The ext_skel script that created your skeleton myfile project created the necessary code to support global variables. By examining php_myfile.h, you should see a commented section similar to the following:
ZEND_BEGIN_MODULE_GLOBALS(myfile)
int global_value;
char *global_string;
ZEND_END_MODULE_GLOBALS(myfile)
You can uncomment this section and add any global variables you'd like in between the two macros. A few lines down in the file, you'll see that the skeleton script automatically defined a MYFILE_G(v) macro. This macro should be used all over your source code to access these global variables. It will make sure that if you're in a multi-threaded environment, it will access a per-thread copy of these globals. No mutual exclusion is required by you.
The last thing you need to do in order for the global variables to work is to uncomment the following line in myfile.c:
ZEND_DECLARE_MODULE_GLOBALS(myfile)
You might want to initialize your global variables to a default value at the beginning of each PHP request. In addition, if for example, the global variables point to allocated memory, you might also want to free the memory at the end of each request. For this purpose, the global variable mechanism supports a special macro that allows you to register a constructor and destructor function for your global variables (see Table 15.8 for an explanation of its parameters):
ZEND_INIT_MODULE_GLOBALS(module_name, globals_ctor, globals_dtor)
Table 15.8. ZEND_INIT_MODULE_GLOBALS Macro ParametersParameter | Meaning |
---|
module_name | The name of your extension as passed to the ZEND_BEGIN_MODULE_GLOBALS() macro. In our case, myfile. | globals_ctor | The constructor function pointer. In the myfile extension, the function prototype would be something like
void php_myfile_init_globals(zend_myfile_globals *myfile_globals) | globals_dtor | The destruction function pointer. For example,
void php_myfile_init_globals(zend_myfile_globals *myfile_globals) |
You can see an example of the constructor function and use of the ZEND_INIT_MODULE_GLOBALS() macro in myfile.c.
15.2.7. Adding Custom INI Directives
The INI file (php.ini) implementation allows PHP extensions to register and listen to their own custom INI entries. If these INI entries are assigned a value either by php.ini, Apache's .htaccess, or other configuration methods, the registered INI variable will always be updated with the correct value. This whole INI framework has many different options and allows for a lot of flexibility. We cover the basics (which gives you a good start) and, with the help of the other material in this chapter, allows you to do most of what you'll need for your day-to-day job.
PHP INI directives are registered with the STD_PHP_INI_ENTRY() macro in between the PHP_INI_BEGIN()/PHP_INI_END() macros. For example, in myfile.c you should see something like the following:
PHP_INI_BEGIN()
STD_PHP_INI_ENTRY("myfile.global_value", "42", PHP_INI_ALL, OnUpdateInt,
global_value, zend_myfile_globals, myfile_globals)
STD_PHP_INI_ENTRY("myfile.global_string", "foobar", PHP_INI_ALL, OnUpdateString,
global_string, zend_myfile_globals, myfile_globals)
PHP_INI_END()
Other macros besides STD_PHP_INI_ENTRY() can be used, but this one is the most common and should be sufficient for almost all needs (see Table 15.9 for more information about its parameters):
STD_PHP_INI_ENTRY(name, default_value, modifiable, on_modify, property_name, struct_type,
struct_ptr)
Table 15.9. STD_PHP_INI_ENTRY Macro ParametersParameter | Meaning |
---|
name | Name of the INI entry. | default_value | The default value, if not specified in the INI file. The default value is always specified as a string. | modifiable | A bit field specifying under what circumstances the INI entry can be changed. Possible values are
PHP_INI_SYSTEM. Values can be changed in system files such as php.ini or httpd.conf. PHP_INI_PERDIR. Values can be changed by .htaccess. PHP_INI_USER. Values can be changed by user scripts. PHP_INI_ALL. Values can be changed from everywhere
| on_modify | Callback function that handles the modification for this INI entry. Usually, you will not write your own handlers and will use some of the provided ones. These include
OnUpdateInt OnUpdateString OnUpdateBool OnUpdateStringUnempty OnUpdateReal
| property_name | Name of the variable that should be updated. | struct_type | Type of the structure the variables resides in. You will usually use the global variables mechanism, so the type is usually automatically defined and will be something like zend_myfile_globals. | struct_ptr | The name of the globals structure. By using the global variables mechanism, this would be myfile_globals. |
Finally, to make the INI mechanism work correctly with your INI entries, you need to uncomment the REGISTER_INI_ENTRIES() call in PHP_MINIT_FUNCTION(myfile) and uncomment the UNREGISTER_INI_ENTRIES() call in PHP_MSHUTDOWN_FUNCTION(myfile).
Accessing one of the two sample global variables is as simple as writing MYFILE_G(global_value) and MYFILE_G(global_string) from anywhere in your extension.
If you'd put the following lines in your php.ini, the value of MYFILE_G (global_value) would change accordingly to 99:
; php.ini The following line sets the INI entry myfile.global_value to 99.
myfile.global_value = 99
15.2.8. Thread-Safe Resource Manager Macros
By now, you must have noticed the use of macros here and there starting with TSRM, which stands for Thread-Safe Resource Manager. These macros give your extension the possibility of having its own global variables, as previously mentioned.
When writing a PHP extension, whether in a multi-process or a multi-threaded environment, you access your extension's global variables via this mechanism. If you want to use global variable accessor macros (such as the MYFILE_G() macro), you need to make sure that the TSRM context information is present in your current function. For performance reasons, the Zend Engine tries to pass around this context as a parameter as much as possible, including to your PHP_FUNCTION() definition. For this reason, when writing code that uses the accessor macro (such as MYFILE_G()) in the scope of PHP_FUNCTION(), you don't have to make any special declarations. However, if your PHP function calls other C functions that need access to the global variables, you must either pass that context to the C function as an extra parameter or you must fetch the context that is slower.
To fetch the context, you can just use the TSRMLS_FETCH() at the beginning of a code block in which you need access to the global variables. For example:
void myfunc()
{
TSRMLS_FETCH();
MYFILE_G(myglobal) = 2;
}
If you want your code to be more optimized, it is better to pass the context to your function directly (as mentioned before, it is automatically available to you in PHP_FUNCTION()'s scope). You can do this by using the TSRMLS_C (C for call) and TSRMLS_CC (CC for call and comma) macros. The former should be used when the context is the only parameter, and the latter when it is part of a function that accepts more than one argument. In the latter's case, it may not be the first argument because it places a comma before the context, hence its name.
In the function's prototype, you will respectively use the TSRMLS_D and TSRMLS_DC macros to declare that you're receiving the context.
Here's the previous example re-written to take advantage of passing the context by parameter:
void myfunc(TSRMLS_D)
{
MYFILE_G(myglobal) = 2;
}
PHP_FUNCTION(my_php_function)
{
...
myfunc(TSRMLS_C);
...
}
|