PHP Classes

How to Create a PHP C Extension to Manipulate Arrays - Part 1: Basic Array Class Extension

Recommend this page to a friend!
  Blog PHP Classes blog   RSS 1.0 feed RSS 2.0 feed   Blog How to Create a PHP C...   Post a comment Post a comment   See comments See comments (3)   Trackbacks (0)  

Author:

Viewers: 800

Last month viewers: 33

Categories: PHP Tutorials

In PHP projects, arrays are used every where because they are useful and flexible to store all sorts of data structures.

However, when you need to maximize the performance the manipulation of arrays for specific purposes, you can achieve great gains if you implement a PHP extension written in the C language.

Read this tutorial to learn how to build your own basic array manipulation extension in C.




Loaded Article

Contents

Introduction

Building PHP from the Source

Building a PHP Extension

Brief Introduction to zval and Functions

Defining a Class in Our Extension

D for Dynamic

Conclusion

Introduction

In this part of the article we will consider the example of a simple array class and all the basic concepts you need to know to how to build an extension to access it.

Lets start with a list of useful resources that from here one we will be referencing a lot:

I wrote the code on Ubuntu Linux, so for other Linux distributions (and OS X) you may need minimal changes to make it work, like changing apt-get to rpm or whatever is the package installed used in your distribution. If you want to write under Windows, you need to make more changes not covered in this article.

Building PHP from the Source

To get started you need to download and install the PHP source code so you can compile it with debug support enabled. You can certainly write an extension with the regular PHP version with debug support disabled, but setting a couple of debug flags it becomes more useful to understand and fix any issues you have in your C extension code.

So open your console shell, go to the directory where you are going to get the source code of PHP for instance ~/dev/c/) and retrieve the PHP code from its Git repository.

git clone http://git.php.net/repository/php-src.git
cd php-src

Retrieve to latest PHP 5 branch source code.

git checkout PHP-5.6

Install some required to build tools if you do not have them already installed.

sudo apt-get install build-essential autoconf automake libtool

Now we have to install bison. In Ubuntu version 14 it comes with bison version 3 and PHP does not handle it well. We need a version 2.7.

wget http://launchpadlibrarian.net/ 140087283/libbison-dev_2.7.1.dfsg-1_amd64.deb
wget http://launchpadlibrarian.net/ 140087282/bison_2.7.1.dfsg-1_amd64.deb

sudo dpkg -i libbison-dev_2.7.1.dfsg-1_amd64.deb
sudo dpkg -i bison_2.7.1.dfsg-1_amd64.deb

Since we will build PHP without extensions by default, we do not need libxml2. Otherwise, you need to install libxml2-dev library too.

sudo apt-get install libxml2-dev

Configure the PHP build to enable the debug support and without any extensions. The --prefix parameter specifies the directory in which to we will install PHP.

./buildconf
./configure --disable-all --enable-debug --prefix=$HOME/dev/bin/php
make && make install

Ok, PHP is ready to run. Run it with the -v flag and make sure that we have built what you need and where you want.

~/dev/bin/php/bin/php -v

Building a PHP Extension

"Skeleton" extensions can be quickly generated using ext_skel, which lies in the PHP source directory. We are not going to use ext_skel because it is stuffed us hundreds of unnecessary comments in the generated files.

If you still really want to use ext_skel, then you need to run it with the following parameters: in --extname to specify the extension name, and path to the skeleton folder using --skel .

~/dev/c/php-src/ext/ext_skel --extname=slobel --skel=$HOME/dev/c/php-src/ext/skeleton/

Anyway, you should get a directory with the following files.

slobel/
    .gitignore
    config.m4
    config.w32
    slobel.c
    php_slobel.h

Open config.m4 and enter:

if test "$PHP_SLOBEL" = "yes"; then
  AC_DEFINE(HAVE_SLOBEL, 1, [Whether you have Slobel])
  PHP_NEW_EXTENSION(slobel, slobel.c, $ext_shared)
fi

Going further into config.m4 we will only touch the line with PHP_NEW_EXTENSION, adding to the new files.

Now let us write our main header file extension: php_slobel.h. It must necessarily be called php_%nameextension%.h

#ifndef PHP_SLOBEL_H
#define PHP_SLOBEL_H 1

extern zend_module_entry slobel_module_entry;
#define phpext_slobel_ptr &slobel_module_entry

//If we compile a thread-safe version, connect the appropriate header file.
#ifdef ZTS
#include "TSRM.h"
#endif

#endif

In this file we declare a variable of type zend_module_entry information about our expansion. The name of the variable must be of %nameextension%_module_entry.

Open slobel.c and write code into it as follows.

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_slobel.h"

// Define a test function.
PHP_FUNCTION(hello_from_slobel)
{
    // The second parameter specifies that we want to copy the string in memory.
    RETURN_STRING("SLOBEL ENABLED! YEY!", 1);
}

// We give PHP aware of our function, indicating its function table module.
const zend_function_entry slobel_functions[] = {
    PHP_FE(hello_from_slobel,  NULL)
    PHP_FE_END
};

// We define a function that will cause php when connecting our expansion.
PHP_MINIT_FUNCTION( slobel_init )
{
    return SUCCESS;
}


zend_module_entry slobel_module_entry = {
    STANDARD_MODULE_HEADER,
    "slobel", // the name of the extension.
    slobel_functions,
    PHP_MINIT(slobel_init),
    NULL, // MSHUTDOWN
    NULL, // RINIT
    NULL, // RSHUTDOWN
    NULL, // MINFO
    "0.1", //version of the extension.
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_SLOBEL
ZEND_GET_MODULE(slobel)
#endif

The main thing here is the definition of a variable with information about this module in which we have the function table, start function and other necessary data, or not specified setting the fields to NULL in that case. The ZEND_GET_MODULE macro just creates the function get_module for our library, which returns the variable slobel_module_entry;

Ok, now we are ready to build our extension. Run phpize, which will generate for us the configuration files for the garbage collector configs for expansion.

~/dev/bin/php/bin/phpize

And then build extension. The option --with-php-config file, specifies the path to the php-config command that we got when we compiled PHP with debug support enabled.

./configure --with-php-config= $HOME/dev/bin/php/bin/php-config
make && make install

If all built without errors, then run with PHP with extension. If not, the code will still run.

~/dev/bin/php/bin/php -dextension=slobel.so --r "hello_from_slobel();"
SLOBEL ENABLED! YEY!

Brief Introduction to zval and Functions

Before we get to classes, lets take a brief look to what PHP functions and variables provides.

To declare a function, use the macro PHP_FUNCTION, PHP_NAMED_FUNCTION or PHP_METHOD. The only difference is the name of the resulting function.

void prefix_name(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used void ***tsrm_ls)

where:

  • ht - number of arguments with which the function is called;
  • return_value - a pointer to a variable to which the result is written;
  • return_value_ptr - a pointer to a pointer to the output variable (in case you need to return the result by reference);
  • this_ptr - a pointer to the object, if the method is called;
  • return_value_is_used - a flag indicating whether the variable is then recycled;
  • tsrm_ls - Thread Safe Resourse Manager Local Storage! A pointer to a variable;

Function arguments are defined using macros ZEND_ARG_INFO_ *.

//| name of the variable | _ | Does returns Link | number of mandatory variables |
ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 1)
//| Whether the link is passed | argument name |
ZEND_ARG_INFO(0, var1)
ZEND_ARG_INFO(0, var2)
ZEND_END_ARG_INFO()

/*
static const zend_arg_info arginfo_construct[] = {
    { NULL, 0, NULL, 2, 0, 0, 0, 0 },
    { "var1", sizeof("var1")-1, NULL, 0, 0, 0, 0, 0 },
    { "var2", sizeof("var2")-1, NULL, 0, 0, 0, 0, 0 },
}
*/

ZEND_BEGIN_ARG_INFO_EX, ZEND_ARG_INFO ZEND_END_ARG_INFO and will result in an array of structures zend_arg_info. Moreover, the first element of the array is the type zend_internal_function_info.

Other functions defined by the macros PHP_FE, PHP_ME, PHP_ME_MAPPING, are listed in the function table module/class elements such as zend_function_entry.

typedef struct _zend_function_entry {
    const char *fname; // Name functions available in PHP.
    void (*handler)(INTERNAL_FUNCTION_PARAMETERS); // A pointer to a function.
    const struct _zend_arg_info *arg_info; // A pointer to an array of arguments.
    zend_uint num_args; // the number of arguments (in the array pointed to arg_info).
    zend_uint flags; // Various flags.
} zend_function_entry

When you register a module, functions are entered in the global function table (function_table). When you register a class, you add class features to the table.

To get the arguments used by the function zend_parse_parameters, which is called with the following parameters.

  • num_args - the number of arguments.
  • tsrm_ls - described above.
  • type_spec - a string that specifies the types of arguments.
  • ... - Further lists pointers to variables that will be recorded resulting arguments.

To work with variables using PHP zval, which stores a value in itself zvalue_value, its type, the reference count and a flag indicating that it is used here.

struct _zval_struct {
    /* Variable information */
    zvalue_value value;     /* value */
    zend_uint refcount__gc;
    zend_uchar type;    /* active type */
    zend_uchar is_ref__gc;
};

To allocate memory for zval should use macros ALLOC_ZVAL (just allocates memory), MAKE_STD_ZVAL (ALLOC_ZVAL + initialization values) or others.

This should be done because instead zval ALLOC_ZVAL allocate memory for _zval_gc_info, which further stores information for finding circular references.

To remove a zval it is used the zval_ptr_dtor function. Unlike zval_dtor, zval_ptr_dtor first decrements the reference count and deletes zval only if the counter becomes zero.

It is also necessary to consider that for all values zvalue_value, complicated numbers are stored in pointers. Therefore, if you have two zval references to the same string in memory, the removal of one of them will not make it point to an invalid memory location.

More about the zval can be found in PHP Intenrals book, and about circular references in the Guide to PHP.

Defining a Class in Our Extension

Back to our extension let us add the first class. Create a file and write to slobel_darray.h following.

#ifndef PHP_SLOBEL_DARRAY_H
#define PHP_SLOBEL_DARRAY_H 1

extern zend_class_entry *slobel_darray_ce;

void slobel_darray_init(TSRMLS_D);

#endif

We just declared the class variable slobel_darray_ce zend_class_entry type and function to initialize.

Now create a file slobel_darray.c.

#include "php.h"
#include "slobel_darray.h"

zend_class_entry *slobel_darray_ce;

PHP_METHOD(slobel_darray, sayHello)
{
    RETURN_STRING("Hello from darray!", 1);
}

const zend_function_entry slobel_darray_functions[] = {
    // class name, function name, arginfo, flags.
    PHP_ME(slobel_darray, sayHello, NULL, ZEND_ACC_PUBLIC)
    PHP_FE_END
};

void slobel_darray_init(TSRMLS_D)
{
    zend_class_entry tmp_ce;
    INIT_CLASS_ENTRY(tmp_ce, "SLOBEL\\DArray", slobel_darray_functions);

    slobel_darray_ce = zend_register_internal_class( &tmp_ce TSRMLS_CC);

    return;
}

There is only one interesting function in the slobel_darray_init. First we create a temporary structure for our class tmp_ce and fill it with INIT_CLASS_ENTRY. The second parameter is the name of the class, which will be made available to PHP, including its namespace.

Use of function zend_register_internal_class, which registers our class in the class table (class_table).

Now add the function call to the function slobel_darray_init slobel_init (file slobel.h).

PHP_MINIT_FUNCTION(slobel_init)
{
    slobel_darray_init(TSRMLS_C);
    return SUCCESS;
}

And add a new file in slobel_darray.c config.m4 (a list of files specified without commas).

PHP_NEW_EXTENSION( slobel, slobel.c slobel_darray.c, $ext_shared)

Since we changed the config.m4 file, we need to run one more time phpize.

~/dev/bin/php/bin/phpize --clean
~/dev/bin/php/bin/phpize


./configure --with-php-config= $HOME/dev/bin/php/bin/php-config
make && make install

We make a php script to test our extension (call it original: slobel.php)

<?php
$darray = new \SLOBEL\DArray();

echo $darray->sayHello() . PHP_EOL;
?>

And run the script with our extension:

~/dev/bin/php/bin/php -dextension= slobel.so slobel.php
Hello from darray!

D for Dynamic

With this class, which only knows how to say "Hello", it does not go very far. Especially if it was conceived to work as an array. It is time to consider that array and write the code.

Create a directory ds and add to the file darray.h, which declare the structure and function of our array.

#ifndef PHP_SLOBEL_DS_DARRAY_H
#define PHP_SLOBEL_DS_DARRAY_H 1

#include "php.h"

typedef  struct slobel_ds_darray {
    size_t count; // the number of non-NULL elements.
    size_t length; // the current size of the array
    size_t min_length; // the minimum size of the array
    size_t capacity; // Power - how much will increase the size
    void *elements; // an array of elements (zval)
} slobel_ds_darray;

slobel_ds_darray *slobel_ds_darray_create(size_t size, size_t capacity);
void slobel_ds_darray_destroy(slobel_ds_darray *array);

#define slobel_ds_darray_length(array) ((array)->length)
#define slobel_ds_darray_min_length(array) ((array)->min_length)
#define slobel_ds_darray_capacity(array) ((array)->capacity)
#define slobel_ds_darray_count(array) ((array)->count)
#define slobel_ds_darray_first(array) ((zval *)(array)->elements)

#endif

Now in the file ds/darray.c define the functions declared above. For now it is only for creating and removing structures.

#include "ds/darray.h"
#include "php.h"

slobel_ds_darray *slobel_ds_darray_create( size_t size, size_t capacity) {
    slobel_ds_darray *array = emalloc(sizeof(slobel_ds_darray));
    if (!array) {
        return NULL;
    }

    array->count = 0;
    array->length = 0;
    array->min_length = size;
    array->capacity = capacity;
    array->elements = NULL;


    return array;
}


void slobel_ds_darray_destroy( slobel_ds_darray *array) {
    if (!array) {
        return;
    }

    efree(array);
}

We have a class, there is an array, and we need to somehow tie on concept to the other. First, let us detail how PHP works with objects.

For storing objects in variables (which zval) structure is used zend_object_value, which has the following fields.

typedef struct _zend_object_value {
    zend_object_handle handle;
    const zend_object_handlers *handlers;
} zend_object_value;
  • handlers - structure with function pointers, which allows PHP when we do something with the objects (read property, call the method, and the like). More information about zend_object_handlers is covered later.
  • handle - it is a regular int, «id» object in the object store object_store. When an object is created PHP places in the structure of zend_object object_store and returns us an integer handle. Thus, if a class has a function create_object, it is called during the creation of a zend_object. More information about this can be found in the PHP internals book.

So, all we need now is to define the structure that extends zend_object. To do this, write your own create_object function and a function to release the memory allocated for the structure. Add them after the declaration of slobel_darray_ce.

zend_object_handlers slobel_darray_handlers;

typedef struct slobel_darray {
    zend_object std;
    slobel_ds_darray *array;
} slobel_darray;

static void slobel_darray_free_object_storage( slobel_darray *intern TSRMLS_DC)
{
    zend_object_std_dtor( &intern->std TSRMLS_CC);

    if (intern->array) {
        slobel_ds_darray_destroy(intern->array);
    }

    efree(intern);
}

zend_object_value slobel_darray_create_object( zend_class_entry *class_type TSRMLS_DC) {
    zend_object_value retval;

    slobel_darray *intern = emalloc( sizeof(slobel_darray));
    memset(intern, 0, sizeof(slobel_darray));

    // Initializes the object: indicates reference to the class, and resets the other fields.
    zend_object_std_init( &intern->std, class_type TSRMLS_CC);
    // Copy the properties of the class object.
    object_properties_init( &intern->std, class_type);

    // Adds an object to the object store (object_store).
    retval.handle = zend_objects_store_put(
        intern,
        // Standard object's destructor.
        (zend_objects_store_dtor_t) zend_objects_destroy_object,
        //our function to release memory object.
        (zend_objects_free_object_storage_t) slobel_darray_free_object_storage, 
        NULL // copy function, we do not need.
        TSRMLS_CC
    );

    //refers to a structure with a handler function for objects.
    retval.handlers = &slobel_darray_handlers;

    return retval;
}

zend_objects_store_put takes three functions:

  • dtor (zend_objects_destroy_object) - the object's destructor to be called when the reference count on the object becomes 0, or at the end of the script. The destructor is also responsible for the execution of custom code (__destruct). At the same time, in a particular case, PHP can omit the destructor call (for example, if when you called __destruct it was thrown exception or called exit ())
  • free_storage (zend_objects_free_object_storage) - a function that frees the memory allocated for the object (zend_object). Called always either when the reference count on the object becomes 0, or at the end of the script
  • clone - a function that is called when an object is copied. By default it is ignored, and that means you must explicitly call the copy handler function zend_objects_store_clone_obj. It is easier to just pick up and use the own function immediately. Therefore, in 99% of cases just set clone to NULL

In the function slobel_darray_init add the following lines:

// Specify your own function to create zend_object
slobel_darray_ce->create_object = slobel_darray_create_object;

// Copy the default handlers for objects
memcpy( &slobel_darray_handlers, zend_get_std_object_handlers(), sizeof( zend_object_handlers ));

But where is the array? We will create the array in the constructor.

slobel_darray.c:

PHP_METHOD(slobel_darray, __construct)
{
    slobel_darray *intern;
    long size = 0;
    long capacity = 0;

    zend_error_handling error_handling;

    // Replace error handler so that when an error obtaining constructor arguments were thrown exception.
    zend_replace_error_handling(EH_THROW, NULL, &error_handling TSRMLS_CC);

    // The third parameter specifies the types of variables (l - long), the next - pointers to variables.
    if (zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "ll", &size, &capacity) == FAILURE) {
        zend_restore_error_handling( &error_handling TSRMLS_CC);
        return;
    }

    // Restores the default error handler.
    zend_restore_error_handling( &error_handling TSRMLS_CC);

    if (size <= 0) {
        zend_throw_exception(NULL, "Array size must be positive", 0 TSRMLS_CC);
        return;
    }

    if (capacity < 0) {
        zend_throw_exception(NULL, "Array capacity must be positive or 0", 0 TSRMLS_CC);
        return;
    }

    // We get the facility handle.
    intern = zend_object_store_get_object( getThis() TSRMLS_CC);

    intern->array = slobel_ds_darray_create( (size_t)size, (size_t)capacity);
    if (!intern->array) {
        zend_throw_exception(NULL, "Failed to allocate array", 0 TSRMLS_CC);
    }

    return;
}

Add __construct to the function table:

ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 1)
// if passed by reference, the argument name.
ZEND_ARG_INFO(0, size)
ZEND_ARG_INFO(0, capacity)
ZEND_END_ARG_INFO()

const zend_function_entry slobel_darray_functions[] = {
    PHP_ME( slobel_darray, __construct, arginfo_construct, ZEND_ACC_PUBLIC)
    PHP_ME( slobel_darray, sayHello, arginfo_void, ZEND_ACC_PUBLIC)
    PHP_FE_END
};

Now it is time to build extension and make sure that all is compiled normally. Run phpize, to pick up the changes config.m4. I promise this is the last time.

~/dev/bin/php/bin/phpize --clean
~/dev/bin/php/bin/phpize

./configure --with-php-config= $HOME/dev/bin/php/bin/php-config
make && make install

And run the test script

~/dev/bin/php/bin/php -dextension=slobel.so slobel.php

Conclusion

In the first part of this article we learned how to create PHP extensions in C and how to implement classes and methods.

In the next part we will conclude by learning how to implement typical interfaces that make PHP classes be handled like regular arrays, such as the ArrayAccess and Traversable.

If you liked this article so far, or you have questions, submit a comment here.




You need to be a registered user or login to post a comment

1,611,040 PHP developers registered to the PHP Classes site.
Be One of Us!

Login Immediately with your account on:



Comments:

2. Excelent!!! - Pablo Lagos (2015-08-12 18:03)
Thanks for this tutorial... - 0 replies
Read the whole comment and replies

1. Suggestion -consider implementing APL arrays - Gary Bickford (2015-08-11 20:12)
APL array math is very fast, very rigorous, very powerful... - 1 reply
Read the whole comment and replies



  Blog PHP Classes blog   RSS 1.0 feed RSS 2.0 feed   Blog How to Create a PHP C...   Post a comment Post a comment   See comments See comments (3)   Trackbacks (0)