PHP Extensions: Creating Constants

by BlakeS on December 18, 2008

When creating extensions, it is possible to provide the end-programmer with new constants. Such constants may be then used as parameters to extension functions or to compare against return values. The following table shows the macros that are available:

Macro Description
REGISTER_LONG_CONSTANT(name, value, flags)
REGISTER_MAIN_LONG_CONSTANT(name, value, flags)
Registers a new constant of type long.
REGISTER_DOUBLE_CONSTANT(name, value, flags)
REGISTER_MAIN_DOUBLE_CONSTANT(name, value, flags)
Registers a new constant of type double.
REGISTER_STRING_CONSTANT(name, value, flags)
REGISTER_MAIN_STRING_CONSTANT(name, value, flags)
Registers a new constant of type string. The specified string must reside in Zend’s internal memory.
REGISTER_STRINGL_CONSTANT(name, value, length, flags)
REGISTER_MAIN_STRINGL_CONSTANT(name, value, length, flags)
Registers a new constant of type string. The string length is explicitly set to length. The specified string must reside in Zend’s internal memory.

Table 9: Macros for Creating Constants

There are two types of macros – REGISTER_*_CONSTANT and REGISTER_MAIN_*_CONSTANT. The former type creates constants bound to the extension module. These constants are dumped from the symbol table as soon as the module that registered the constant is unloaded from memory. The latter type creates constants that persist in the symbol table independently of the module.

Each of the macros above accepts a name and value (and a length for the STRINGL versions) and a set of flags. The flags available for use are:

  • CONST_CS – This constant’s name is to be treated as case sensitive
  • CONST_PERSISTENT – This constant is persistent and won’t be forgotten when the current process carrying this constant is shut down

A simple usage example is shown below:

1
2
// register a new constant of type "long"
REGISTER_LONG_CONSTANT("MY_CONSTANT", 1234, CONST_CS | CONST_PERSISTENT);

{ 0 comments }

Twhirl on Ubuntu

by BlakeS on December 17, 2008

For the past three or four days, I’ve been completely disconnected from the rest of the world because I couldn’t get Twhirl 0.8.7 to work on my Ubuntu 8.04 computer. I tried several methods to use the updated AIR file, but had no success. I didn’t realize when I started that not only did I need to upgrade Twhirl, but I also need to update to the latest Adobe Air for Ubuntu.

Here are the steps I used to finally get it working:

  1. Uninstall Adobe Air alpha
  2. Install Adobe Air beta
  3. Install Twhirl 0.8.7

Seems obvious enough, right? Well, unfortunately I couldn’t find an obvious way to uninstall Adobe Air. I looked online, tried to use –help on the command line for the Adobe Air alpha binary, and did a quick Google. Being in a hurry (because who isn’t in a hurry to reconnect on Twitter?), I just started trying random commands. Magically, it worked. Here’s what I did:

sudo ./adobeair_linux_a1_033108.bin -uninstall
sudo ./adobeair_linux_b1_091508.bin

Then double-clicking the AIR file for Twhirl 0.8.7 worked like a charm. The only remaining caveat is to make sure you use the Twhirl 0.8.7 version that was released for Adobe Air 1.1 (http://www.twhirl.org/files/twhirl-0.8.7-air11.air) and not the general release version targeting Adobe Air 1.5.

The Adobe Air beta for Linux can be found here: http://labs.adobe.com/downloads/air_linux.html

{ 0 comments }

PHP Extensions: Implementing the Extension

by BlakeS on December 16, 2008

So far, we haven’t examined the source or header files that were created by the ext_skel script. Now it’s time to dive in and make them useful. The header file is a very standard PHP extension header file and requires no modification for this particular extension. The header file will be revisited in the full example later in this document.

The implementation file, first_test.c, must be edited to provide the desired functionality. To begin, locate the implementation of the calcpi function. Initially, the implementation looks like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
{{{ proto double calcpi(int iterations)
    Calculate Pi */
PHP_FUNCTION(calcpi)
{
     int argc = ZEND_NUM_ARGS();
     long iterations;
 
     if (zend_parse_parameters(argc TSRMLS_CC, "l", &iterations) == FAILURE)
          return;
 
     php_error(E_WARNING, "calcpi: not yet implemented");
}
/* }}} */

Code Fragment 4: Unedited implementation of the calcpi() function.

Lines 1, 2 and 13 are comments that provide some human readable breaks and documentation within the source. Line 3 is the function prototype, simplified by a C macro. Lines 5 and 6 declare some required local variables and line 11 is the line that provides the warning message indicating that the function is not yet implemented. The most valuable line that was automatically generated is line 8. This single line is the function that ensures that the parameters passed from the PHP script are consistent with the function prototype and then populates the local C variables appropriately. This function will be discussed in greater detail later.

To make this function actually do what we want, we must provide a real implementation and remove the extraneous warning. Using a simplistic PI calculation algorithm, the function becomes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 /* {{{ proto double calcpi(int iterations)
    Calculate Pi */
 PHP_FUNCTION(calcpi)
 {
     int argc = ZEND_NUM_ARGS();
     long iterations;
     int index, hits;
     double randx, randy, distance, value;
 
     if (zend_parse_parameters(argc TSRMLS_CC, "l", &iterations) == FAILURE)
          return;
 
     hits = 0;
     for ( index = 0; index < iterations; index++ )
     {
       randx = rand();
       randy = rand();
 
       randx /= RAND_MAX;
       randy /= RAND_MAX;
       distance = sqrt( ( randx * randx ) + (randy * randy ) );
 
       if ( distance <= 1.0 )
       {
          hits++;
       }
       value = ( (double) hits / (double) index );
       value *= 4.0;
     }
 
     value = ( (double) hits / (double) iterations );
     value *= 4.0;
     RETVAL_DOUBLE( value );
 }
 /* }}} */

Code Fragment 5: Full implementation of the calcpi() function.
As can be seen from the above code, the warning message has been removed and a simple PI
calculation algorithm has been added. The most significant change from the extension perspective is
line 35, which assigns the return value and indicates its type as DOUBLE.

Now calling the function:

echo '' | ./sapi/cli/php

Returns the following (or similar, based on the calculated value):

3.15

The implementation for the reverse function is below. Note that none of the implementations for this test extension are to be considered models for efficient C programming.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 /* {{{ proto string reverse(string input)
    Reverse the input string */
 PHP_FUNCTION(reverse)
 {
     char *input = NULL;
     int argc = ZEND_NUM_ARGS();
     int input_len;
     char* workstr;
     int index;
 
     if (zend_parse_parameters(argc TSRMLS_CC, "s", &input, &input_len) == FAILURE)
          return;
 
     workstr = (char*) emalloc(input_len + 1);
     memset( workstr, 0, input_len + 1 );
     for ( index = 0; index < input_len; index++ )
     {
       workstr[index] = input[input_len - ( index + 1 )];
     }
 
    RETVAL_STRING( workstr, 1 );
 
     efree( workstr );
 }
 /* }}} */

Code Fragment 6: Implementation of the reverse() function.

This function demonstrates two things. First, this function demonstrates using the emalloc and efree memory management routines in place of the standard C malloc/free functions. These memory management routines are preferred when developing PHP extensions as they allow the Zend engine to manage the entire memory pool which allows the engine to determine when a block is in use and automatically free unused blocks and blocks with lost references, preventing memory leaks. There are several more memory management routines available for your use, all of which will be discussed later.

The second concept demonstrated in the above example is how to return a string to PHP. The RETVAL_STRING macro accepts two parameters, the string itself (character pointer) and a Boolean flag indicating whether or not to duplicate the string using estrdup. In the above example, I allocate the memory for a working buffer, create the reversed string and then set the return value using the flag to duplicate the string. Lastly, I free the working buffer. Alternately, I could have simply allocated the working buffer and returned it directly without duplication, but I personally feel it is better to free memory I allocate for internal use and let the Zend engine deal with freeing its own internal memory allocation that results from using the RETVAL_STRING macro as I did above. That said, if I were absolutely interested in performance, I would eliminate the additional memory allocation and buffer copying and simply note my reasoning in the code.

The implementation of the uniquechars function is below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/* {{{ proto array uniquechars(string input [, bool case_sensitive])
   Return the unique characters in the input string */
PHP_FUNCTION(uniquechars)
{
     char *input = NULL;
     int argc = ZEND_NUM_ARGS();
     int input_len;
     zend_bool case_sensitive;
     char* workbuf;
     int index, work_index;
 
     if (zend_parse_parameters(argc TSRMLS_CC, "s|b", &input, &input_len, &case_sensitive) == FAILURE)
         return;
 
     if ( argc == 1 )
     {
       case_sensitive = 1;
     }
     work_index = 0;
     workbuf = (char*) emalloc( input_len + 1 );
     memset( workbuf, 0, input_len + 1 );
     for ( index = 0; index < input_len; index++ )
     {
       if ( case_sensitive )
       {
         if ( !strchr( workbuf, input[index] ) )
         {
            workbuf[work_index] = input[index];
            work_index++;
         }
       }
       else
       {
         if ( !strchr( workbuf, tolower( input[index] ) ) )
         {
              workbuf[work_index] = tolower( input[index] );
              work_index++;
           }
         }
       }
 
       array_init( return_value );
       for ( index = 0; index < input_len; index++ )
       {
         if ( workbuf[index] != '\0' )
         {
           add_next_index_stringl( return_value, &workbuf[index], 1, 1 );
         }
       }
 
      efree( workbuf );
 }
 /* }}} */

Code Fragment 7: Implementation of the uniquechars() function.

The above function works by first creating a standard C character array containing all of the unique characters in lines 21 through 42. It sets the case_sensitive flag to the default (true) in lines 15 through 18 if the parameter is not passed at all. The really interesting code, though, is in lines 44 through 50 where the PHP array variable is created and populated.

Line 44 initializes the return value as an array. In the previous two examples, the return_value variable was used opaquely in the RETVAL_XXX functions, but it is important to note that every function that is prototyped using the PHP_FUNCTION macro has a standard return variable called return_value. It is easiest to manipulate this value using the RETVAL_XXX or RETURN_XXX macros, but those macros do not provide array functionality.

Once the array variable is initialized, items are added to the array (in this example) using the add_next_index_stringl function. This function takes four parameters:

  1. The array variable itself
  2. A character pointer representing the string to add
  3. The length of the string being added
  4. Whether to duplicate the string being added

There are many array manipulation functions available, all of which are discussed in depth later in this document.

Now that the new extension is fully implemented, it becomes possible to test it in PHP. The following is a sample PHP script and the output generated by the script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
   print( 'Calculating PI using 10 iterations: ' . calcpi( 10 ) . "\n" );
   print( 'Calculating PI using 100 iterations: ' . calcpi( 100 ) . "\n" );
   print( 'Calculating PI using 1,000 iterations: ' . calcpi( 1000 ) . "\n" );
   print( 'Calculating PI using 10,000 iterations: ' . calcpi( 10000 ) . "\n" );
   print( 'Calculating PI using 100,000 iterations: ' . calcpi( 100000 ) . "\n" );
   print( "\n" );
 
   print( 'Reverse of "Zend Engine" is "' . reverse( 'Zend Engine' ) . "\"\n" );
   print( "\n" );
 
   print( 'The unique characters in "Zend Engine" (case sensitive) are: ' . implode( ',', uniquechars( 'Zend Engine' ) ) . "\n" );
   print( 'The unique characters in "Zend Engine" (case insensitive) are: ' . implode(',', uniquechars( 'Zend Engine', false ) ) . "\n" );
   print( "\n" );
?>

Code Fragment 8: PHP script for testing the first_test extension.

Calculating  PI  using 10 iterations: 3.2
Calculating  PI  using 100 iterations: 3.2
Calculating  PI  using 1,000 iterations: 3.248
Calculating  PI  using 10,000 iterations: 3.1356
Calculating  PI using 100,000 iterations: 3.1358

Reverse of "Zend Engine" is "enignE dneZ"

The unique characters in "Zend Engine" (case sensitive) are: Z,e,n,d, ,E,g,i
The unique characters in "Zend Engine" (case insensitive) are: z,e,n,d, ,g,i

Results/Output 1: Output of the test PHP script.

{ 0 comments }

PHP Extensions: Building the Shell

by BlakeS on December 14, 2008

Navigate to the PHP installation directory, then to theext directory. For example:

  cd /path/to/php-4.x.x/ext

Run the ext_skel script in that directory with no parameters. This is the script that will create the initial files for a new PHP extension. Its parameters are shown in the table below.

–extname=module module is the name of your extension
–proto=file file contains prototypes of functions to create
–stubs=file Leave out also all module specific stuff and write just function stubs with function value declarations and passed argument handling, and function entries and definitions at the end of the file, for copying and pasting into an already existing module.
–xml generate xml documentation to be added to phpdoc-cvs
–skel=dir path to the skeleton directory
–full-xml generate xml documentation for a self-contained extension (not yet implemented)
–no-help By default, ext_skel creates both comments in the source code and a test function to help first time module writers to get started and testing configuring and compiling their module. This option turns off all such things which may just annoy experienced PHP module coders. Especially useful with the –stubs parameter.

Table 1: Parameters to the ext_skel script.

NOTE: I suggest you read the README.EXT_SKEL file in the main PHP install path for details about the script.

For our first extension, we will be creating some simple functions:

double calcpi( int iterations )
This function returns an approximation of pi. The algorithm is a simplistic random-number method of calculating pi which is neither efficient nor particularly accurate – but it is fun.

string reverse( string input )
This function returns the reverse version of a string. For example “Test String” becomes “gnirtS tseT”.

array uniquechars( string input, [bool case_sensitive] )
Returns an array of all the unique characters in the input string. For example “Test String” returns an array containing the elements ‘T’, ‘e’, ‘s’, ‘t’, ‘S’, ‘r’, ‘i’, ‘n’ and ‘g’. The case_sensitive parameter which is optional and will be implicitly true sets a flag which affects whether the search for characters should be case sensitive. If case_sensitive is false, the previous example will return ‘t’, ‘e’, ‘s’, ‘r’, ‘i’, ‘n’ and ‘g’ – not including the uppercase/lowercase versions of the characters.

None of the above functions are particularly useful, but as examples of creating PHP extensions, they
provide coverage of many of the features that will be used in a more complete extension.

First, create a text file containing the function prototypes. This file should contain one function prototype per line. For the example functions, the file should contain the following three lines (the last
line may wrap in this view, but should not wrap in the actual file you create):

double calcpi( int iterations) Calculate Pi
string reverse( string input ) Reverse the input string
array uniquechars( string input [, bool case_sensitive] ) Return the unique characters in the input string

Code Fragment 1: Prototype file for first_test.

Call this file first_test.proto. The ext_skel script will read this file and parse it to build the skeleton C code for the extension. It is not necessary to build a prototype file, but doing so will greatly reduce the amount of actual C coding required to continue and will decrease the risk of incorrectly prototyping the C functions as required to work within the PHP framework.

Each line of the prototype file contains information about a function that will be visible within PHP. The
format of each line is:

[function return type] function name (parameter list ) optional comment

The function return type is optional and has no effect on the generated skeleton code except in the internal documentation. The parameter list is a comma-separated list consisting of a type and a parameter name. Parameters may be optionally enclosed in square brackets signifying an optional parameter in the PHP context.

With the prototype file created, it is time to run the ext_skel script. Execute the script using:

cd /path/to/php/ext
./ext_skel --extname=first_test --proto=first_test.proto

Assuming you have the correct write permissions for your system, the script will run and report a list of instructions required to now build and install the new extension. The steps are:

cd ..
vi ext/first_test/config.m4
./buildconf
./configure --[with|enable]-first_test
make
./php -f ext/first_test/first_test.php
vi ext/first_test/first_test.c
make

I suggest following these steps almost verbatim to ensure that the extension properly builds and integrates into PHP. Before doing so, however, it is interesting to see the files that have been created in the /path/to/php/ext/first_test directory. The files are:

config.m4 This file is used by the buildconf script later to integrate the new extension into the PHP configure script.
CREDITS An essentially empty text file for you to credit yourself for your great work.
EXPERIMENTAL An empty text file.
first_test.c The implementation of the new extension.
first_test.php A PHP test script for the new extension.
php_first_test.h The header file of the new extension.
tests A directory containing at least one PHP test file for use with the automated build testing command make test.

Table 2: Files generated by the ext_skel script.

{ 0 comments }

Tiny jQuery method to restripe a table

by BlakeS on December 11, 2008

I know that the HTML table is the bane of human existence and all of that, but assuming you’re using a table to present tabular data (and not for layout, you animated-gif lover), you probably use CSS classes to stripe alternating rows of your table for easier reading.

Creating a table with simple row stripes in PHP or any other back-end programming language is very simple and requires no front-end Javascript, but if you provide front-end methods for sorting or changing the data in the table, a JS restripe function may be handy.

Here’s a really simple example of a Javascript restripe function based on jQuery:

1
2
3
4
5
6
7
8
9
10
    function restripe(table_id, alt_row_class) {
      var i = 1;
      jQuery('#' + table_id + ' tr').each(function() {
        jQuery(this).removeClass(alt_row_class);       
 
        if (i++ % 2) {
          jQuery(this).addClass(alt_row_class);
        }
      })
    };

The function takes two arguments. The first is the id of the table and the second is the CSS class name for the alternate row. This function very simply iterates over each row in the table and first removes the special alternate row class name, then adds it back for every other row.

Obviously this is not a general-purpose solution, but as a starting point, it provides all of the fundamentals from which more generalized versions can be adapted.

{ 0 comments }

Creating Your Own PHP Extension: Motivation

by BlakeS on December 10, 2008

Most PHP developers will not need to develop their own custom extension. PHP is a rich programming environment which is constantly updated through a strong open source development community. However, there are times when developing a custom extension may be necessary. Additionally, understanding the extension mechanism in PHP can provide a broader general understanding of PHP itself.

I have written two custom extensions with practical benefits in the very recent past. The first was written to map some proprietary C-based financial code into PHP for an example Intranet-based web site. The second was an implementation of a TNEF mail attachment decoder for a web-based email project.

Because of the nature of this document, it is assumed that the reader already has an understanding of PHP development, has built PHP from source and has a C programming background. You should also be familiar with PHP and its relationship to the Zend engine. Much of this document assumes you understand the relationship and so I often do not differentiate between the two in this text. This is an advanced topic not typical to general PHP programming.

{ 0 comments }

Procedural DB Access vs. PEAR::DB

by admin on November 19, 2008

This section shows the similarities and the differences between using procedural database access and PEAR::DB. The example is deliberately simple, showing how to establish a database connection, retrieve a small recordset and close the connection.

The example uses MySQL as the database backend, but could conceivably be any PHP-supported database.

For the examples, a simple four-table database will be used to illustrate a small employee and department data set. The four tables are Employees, Paychecks, EmployeesToDepartments, and Departments. The MySQL queries to create and populate the tables are shown below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#
# Table structure for table `Departments`
#
 
CREATE TABLE `Departments` (
`id` int(11) NOT NULL default '0',
`name` varchar(50) NOT NULL default '',
PRIMARY KEY (`id`)
) TYPE=MyISAM;
 
#
# Dumping data for table `Departments`
#
 
INSERT INTO `Departments` VALUES (1, 'Web Development');
INSERT INTO `Departments` VALUES (2, 'QA');
 
#
# Table structure for table `Employees`
#
 
CREATE TABLE `Employees` (
  `id` int(11) NOT NULL default '0',
  `last_name` varchar(50) NOT NULL default '',
  `first_name` varchar(50) NOT NULL default '',
  `phone_ext` varchar(5) NOT NULL default '',
  `mgr_id` int(11) NOT NULL default '0',
  `dept_id` int(11) NOT NULL default '0',
  PRIMARY KEY (`id`),
  KEY `mgr_id` (`mgr_id`),
  KEY `dept_id` (`dept_id`)
) TYPE=InnoDB;
 
#
# Dumping data for table `Employees`
#
 
INSERT INTO `Employees` VALUES (1, 'Schwendiman', 'Blake', '204', 2, 1);
INSERT INTO `Employees` VALUES (2, 'Cox', 'Jeffrey', '225', 0, 1);
INSERT INTO `Employees` VALUES (3, 'McNulty', 'Kate', '214', 2, 2);
 
#
# Table structure for table `Paychecks`
#
 
CREATE TABLE `Paychecks` (
   `id` int(11) NOT NULL auto_increment,
   `employee_id` int(11) NOT NULL default '0',
   `check_date` date NOT NULL default '0000-00-00',
   `check_number` int(11) NOT NULL default '0',
   `gross_pay` float NOT NULL default '0',
   `benefits_withheld` float NOT NULL default '0',
   `taxes_withheld` float NOT NULL default '0',
   PRIMARY KEY (`id`)
) TYPE=MyISAM AUTO_INCREMENT=1 ;
 
CREATE TABLE `EmployeesToDepartments` (
   `emp_id` int(11) NOT NULL default '0',
   `dept_id` int(11) NOT NULL default '0',
   `date_xferred` date NOT NULL default '0000-00-00',
   PRIMARY KEY (`emp_id`,`dept_id`)
) TYPE=MyISAM;

Code Listing 1: MySQL Statements to Create Sample Database

The Employees table contains a field called dept_id representing the current department in which the employee works. The EmployeesToDepartments table is an intermediate table to effect an m-to-n relationship between Employees and Departments representing historical information about which employees have ever worked in a department. The real purpose of the EmployeesToDepartments table, though, is for examples in which a multiple-field key is needed.

The sample code will use the SQL query shown in Code Listing 2 below to retrieve data about employees, their department and their manager. The query is deliberately more complex than the most basic SELECT statement to provide more value to the example, but the details of the SQL statement will not be covered in this document.

1
2
3
4
5
6
7
8
9
10
SELECT Employees.last_name as emp_last_name,
Employees.first_name as emp_first_name,
Employees.phone_ext as emp_phone_ext,
Departments.name as dept_name,
Employees_1.last_name as mgr_last_name,
Employees_1.first_name as mgr_first_name,
Employees_1.phone_ext as mgr_phone_ext FROM
(Departments INNER JOIN Employees ON Departments.id
= Employees.dept_id) LEFT JOIN Employees AS
Employees_1 ON Employees.mgr_id = Employees_1.id

Code Listing 2: SQL Query for Sample Employee Database

Both the procedural example and the PEAR::DB example will retrieve the data and create an HTML table containing the results.

{ 0 comments }

Installing PEAR::DB

by BlakeS on November 14, 2008

If you do not have the DB package installed, using
the following command to install DB:

  pear install DB

If you do not have the most current version of DB or
if you need to check for an upgrade and install it, use:

  pear upgrade DB

Note that the above will cause an upgrade to be
installed if one exists. If you simply wish to check for
upgrades, use the pear list-all or pear list-upgrades
commands.

For the purposes of this document, it will be assumed
that the version of PEAR::DB is 1.5.0RC2.

{ 0 comments }

Installing PEAR

by BlakeS on September 16, 2008

Depending on your PHP version and your computing
platform, there may be some steps needed to install
and configure PEAR.

If you are running a version of PHP prior to version
4.3, you will likely need to manually install the PEAR
package manager. Later versions of PHP (4.3 and
higher) come with the PEAR package manager or the
package manager is built be default. If you added the
–without-pear flag to your configuration of PHP,
you will also need to get the PEAR package manager.
For Unix, Linux and BSD you can obtain the PEAR
package manager using the command line:

  lynx –source http://go-pear.org/ | php

The above command line requires that you have lynx
installed (it may be called links on some Linux
distributions). It is also assumed that you have your
PHP binary built and installed.

{ 0 comments }