14.3. Building Your First ExtensionsThis section walks you through the steps of building your first extension, from design through testing. Most extensions are created by writing a file that defines the functions the extension will have, building a skeleton from that, and then filling in the C code that does the actual work of the extension. This section doesn't cover advanced topics such as returning complex values or managing memory—we'll talk about those later, after you have the basics down. 14.3.1. Command-Line PHPUnless your extension can really be tested only through the Web, it is much easier to debug and quickly test your code through the command-line version of PHP (also sometimes referred to as the CGI version of PHP). To build the command-line version, do something like this: % cd php4 % ./configure --with-mysql=/usr --with-pgsql --with-zlib --with-config-file=/etc % make # make install This will put a php binary in your /usr/local/bin directory. The configure line above adds MySQL, PostgreSQL, and zlib support. While you don't need them to develop your extension, they won't get in the way, and it is a good idea to have a php binary that can run complex web applications directly from the command line. Just to make sure it worked, test it: % /usr/local/bin/php -v 4.2.0-dev 14.3.2. Planning Your ExtensionAs much as you probably just want to dive in and start coding, a little bit of planning ahead of time can save you a lot of time and headaches later. The best way to plan your extension is to write a sample PHP script that shows exactly how you plan to use it. This will determine the functions you need to implement and their arguments and return values. For example, take a fictitious rot13[8] extension that might be used as follows:
<?php echo rot13($string); ?> From this we see that we need to implement a single function, which takes a string as an argument and returns a string. Don't let the simplicity of the example fool you—the approach we'll take holds for extensions of any complexity. 14.3.3. Creating a Skeleton ExtensionOnce you have planned your extension, you can build a skeleton with the ext_skel tool. This program takes a .def file, which describes the functions your extension will provide. For our example, rot13.def looks like this: string rot13(string arg) Returns the rot13 version of arg This defines a function that returns a string and takes a string argument. Anything after the close parenthesis is a one-line description of the function. The other types valid in a .def file are:
Let's look at the basic structure of a PHP extension. Create one for yourself and follow along: % cd php4/ext % ./ext_skel --extname=rot13 --proto=rot13.def % cd rot13 Running ext_skel like this creates the following files:
14.3.4. Fleshing Out the SkeletonThe rot13.c file contains the C code that implements the extension. After including a standard collection of header files, the first important part of the extension is: /* {{{ rot13_functions[] * * every user-visible function must have an entry in rot13_functions[] */ function_entry rot13_functions[] = { PHP_FE(confirm_rot13_compiled, NULL) /* for testing; remove later */ PHP_FE(rot13, NULL) {NULL, NULL, NULL} /* must be the last line in rot13_functions[] */ }; /* }}} */ The {{{ and }}} sequences in the comments don't have meaning to the C compiler or PHP—they indicate a "fold" to editors that understand text folding. If your editor supports it (Vim6 and Emacs do), you can represent a block of text (e.g., a function definition) with a single line (e.g., a description of the function). This makes it easier to edit large files. The important part in this code is the function_entry array, which lists the user-visible functions that this extension implements. Two such functions are shown here. The ext_skel tool generated the confirm_rot13_compiled( ) function for the purposes of testing. The rot13( ) function came from the definition in rot13.def. PHP_FE( ) is a macro that stands for PHP Function Entry. The PHP API has many such convenience macros. While they speed up development for programmers experienced with the API, they add to the learning curve for beginners. Next comes the zend_module_entry struct: zend_module_entry rot13_module_entry = { STANDARD_MODULE_HEADER, "rot13", rot13_functions, PHP_MINIT(rot13), PHP_MSHUTDOWN(rot13), PHP_RINIT(rot13), /* replace with NULL if no request init code */ PHP_RSHUTDOWN(rot13), /* replace with NULL if no request shutdown code */ PHP_MINFO(rot13), "0.1", /* replace with version number for your extension */ STANDARD_MODULE_PROPERTIES }; This defines the functions to be called for the various stages of startup and shutdown. Like most extensions, rot13 doesn't need per-request startup and shutdown functions, so follow the instructions in the comments and replace PHP_RINIT(rot13) and PHP_RSHUTDOWN(rot13) with NULL. The resulting zend_module_entry struct looks like this: zend_module_entry rot13_module_entry = { STANDARD_MODULE_HEADER, "rot13", rot13_functions, PHP_MINIT(rot13), PHP_MSHUTDOWN(rot13), NULL, NULL, PHP_MINFO(rot13), "0.1", /* replace with version number for your extension */ STANDARD_MODULE_PROPERTIES }; The extension API changed between PHP 4.0.x and PHP 4.1.x. To make your extension be source-compatible with PHP 4.0.x, you need to make some of the elements of the structure conditional, as follows: zend_module_entry rot13_module_entry = { #if ZEND_MODULE_API >= 20010901 STANDARD_MODULE_HEADER, #endif "rot13", rot13_functions, PHP_MINIT(rot13), PHP_MSHUTDOWN(rot13), NULL, NULL, PHP_MINFO(rot13), #if ZEND_MODULE_API >= 20010901 "0.1", #endif STANDARD_MODULE_PROPERTIES }; Next in the rot13.c file is commented code showing how to deal with php.ini entries. The rot13 extension doesn't need to be configured via php.ini, so leave them commented out. Section 14.12 explains the use of these functions. Next comes implementations of the MINIT( ), MSHUTDOWN( ), RINIT( ), RSHUTDOWN( ), and MINFO( ) functions. For our simple rot13 example, we simply need to return SUCCESS from the MINIT( ) and MSHUTDOWN( ) functions, and we can get rid of the RINIT( ) and RSHUTDOWN( ) functions entirely. So, after deleting some commented code, we just have: PHP_MINIT_FUNCTION(rot13) { return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(rot13) { return SUCCESS; } PHP_MINFO_FUNCTION(rot13) { php_info_print_table_start( ); php_info_print_table_header(2, "rot13 support", "enabled"); php_info_print_table_end( ); } When you remove a function (such as RINIT( ) or RSHUTDOWN( )) from rot13.c, be sure to remove the corresponding prototype from php_rot13.h. The MINFO( ) function is called by phpinfo( ) and adds whatever information you want about your extension to the phpinfo( ) output. Finally, we get to the functions that are callable from PHP. The confirm_rot13_compiled( ) function exists only to confirm the successful compilation and loading of the rot13 extension. The skeleton tests use this. Most experienced extension writers remove the compilation-check function. Here is the stub function that ext_skel created for our rot13( ) function: /* {{{ proto string rot13(string arg) returns the rot13 version of arg */ PHP_FUNCTION(rot13) { char *arg = NULL; int argc = ZEND_NUM_ARGS( ); int arg_len; if (zend_parse_parameters(argc TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) return; php_error(E_WARNING, "rot13: not yet implemented"); } /* }}} */ The {{{ proto line is not only used for folding in the editor, but is also parsed by the genfunclist and genfuncsummary scripts that are part of the PHP documentation project. If you are never going to distribute your extension and have no ambitions to have it bundled with PHP, you can remove these comments. The PHP_FUNCTION( ) macro declares the function. The actual symbol for the function is zif_rot13, which is useful to know if you are debugging your code and wish to set a breakpoint. The only thing the stubbed function does is accept a single string argument and then issue a warning saying it hasn't been implemented yet. Here is a complete rot13( ) function: PHP_FUNCTION(rot13) { char *arg = NULL, *ch, cap; int arg_len, i, argc = ZEND_NUM_ARGS( ); if (zend_parse_parameters(argc TSRMLS_CC, "s/", &arg, &arg_len) == FAILURE) return; for(i=0, ch=arg; i<arg_len; i++, ch++) { cap = *ch & 32; *ch &= ~cap; *ch = ((*ch >= 'A')&&(*ch <= 'Z') ? ((*ch-'A'+13) % 26+'A') : *ch)|cap; } RETURN_STRINGL(arg, arg_len, 1); } The zend_parse_parameters( ) function extracts the PHP values passed as parameters to the rot13( ) function. We'll talk about it in depth later. Don't worry too much about the string manipulation and bitwise logic here—that's merely the implementation of the rot13 behavior, not something that'll be in every extension you write. The RETURN_STRINGL( ) call at the end returns the string. You give it the string, the length of the string, and a flag that indicates whether a copy needs to be made. In this case, we need to have a copy made, so the last argument is a 1. Failing to return a copy may lead to memory leaks or crashes, as we'll see in Section 14.5 later. 14.3.5. Compiling Your ExtensionBefore you can build your extension, you must edit the config.m4 file and indicate how the user can specify that the module is to be compiled into PHP. These lines (commented out by default) do just that: PHP_ARG_ENABLE(rot13, whether to enable rot13 support, [ --enable-rot13 Enable rot13 support]) There are two main choices for building your extension. You can make a completely standalone source tree and build your extension as a shared module, or you can work within the framework of the PHP source tree. Shared modules are quicker to compile, but a line in the program source or php.ini file is required to load them. Compiling your extension into PHP takes time, but it means that the extension's functions are always visible to scripts. 14.3.5.1. Standalone extensionsTo create a standalone extension source directory, simply run phpize inside your extension directory. The phpize script should have been installed for you when you did a make install after building PHP earlier. % cd php4/ext/rot13 % phpize This creates a number of files for configuring and building outside the PHP source tree. You can now move this directory anywhere you want. It is a good idea to move it outside of your PHP source tree to prevent a top-level PHP buildconf run from picking it up. To build your extension, simply do: % ./configure % make To use the extension, two things must happen: PHP must be able to find the shared library and must load it. The extension_dir option in php.ini specifies the directory containing extensions. Copy the modules/rot13.so file to that directory. For example, if PHP is looking for extensions in /usr/local/lib/php, use: % cp modules/rot13.so /usr/local/lib/php Either load your extension explicitly (via a function call in every PHP script that wants to use the module), or preload it with a change to the php.ini file. The function call to load your module is: dl('rot13.so'); The extension directive in the php.ini file preloads an extension: extension=rot13.so 14.3.5.2. Compiling the extension into PHPTo compile your extension into PHP, run the following from the top of your PHP4 source tree: % ./buildconf This will add your new --enable-rot13 switch to the top-level PHP ./configure script. You can run the following to verify that it worked: % ./configure --help Now build PHP with: %./configure --enable-rot13 --enable-mysql=/usr .. See Chapter 1 for more information on building and installing PHP from the source code. After you issue a make install, your extension will be built statically into your PHP binary. This means you do not have to load the extension with dl( ) or a change to php.ini; the extension will always be available. Use --enable-rot13=shared on your configure line to force the rot13 extension to be built as a shared library. 14.3.6. Testing Your ExtensionThe test script that is created by the ext_skel program looks like this: <?php if(!extenson_loaded('rot13')) { dl('rot13.so'); } $module = 'rot13'; $functions = get_extension_funcs($module); echo "Functions available in the test extension:<br>\n"; foreach($functions as $func) { echo $func."<br>\n"; } echo "<br>\n"; $function = 'confirm_' . $module . '_compiled'; if (extension_loaded($module)) { $str = $function($module); } else { $str = "Module $module is not compiled into PHP"; } echo "$str\n"; ?> This code checks to see an if the extension is loaded, lists the functions provided by the extension, and then calls the confirmation function if the extension was loaded. This is good, but it doesn't test whether the rot13( ) function works. Modify the test script to look like this: <?php if(!extension_loaded('rot13')) { dl('rot13.so'); } $encrypted = rot13('Rasmus'); $again = rot13($encrypted); echo "$encrypted $again\n"; ?> Run the test with: % ~/php4/ext/rot13> php -q rot13.php Enfzhf Rasmus The test program encrypts "Rasmus", then uses rot13( ) on the string again to decrypt it. The -q option tells the command-line version of PHP to not display any HTTP headers. Copyright © 2003 O'Reilly & Associates. All rights reserved. |
|