PHP Extensions Development

Created by Lorenzo Fontana / @fntlnz


  • Why an extension?
  • PHP Life cycles
  • Build Environment Setup
  • Hello World
  • Memory management
  • Variables and Data Types
  • Objects
  • Debugging

Why an extension?

Link against an external library

Performances optimization

To better understand PHP internals

PHP Life cycles

CLI (Single request life cycle)

CLI Life Cycle

Apache Prefork MPM (Multiprocess life cycles)

MultiProcess Life Cycles

Apache Worker MPM (Multithreaded life cycles)

Multithreaded Life Cycles

Build Environment Setup

Grab the latest PHP version

wget -nv -O - | tar zx
cd php-5.6.3

Configure it for PHP Development

./configure --prefix=/tmp/php-debug --enable-debug --enable-maintainer-zts \
--disable-cgi --enable-cli --disable-pear --with-readline

  • enable-debug :Memory leaks reporting and compile with debugging symbols
  • enable-maintainer-zts : Build with the thread-safe resource manager

Make and install

make -j2
sudo make install

According to our configuration options this will compile PHP and install it into the /tmp/php-debug folder.

To use it temporarily in place of your working PHP just prepend it's location to the $PATH

export $PATH="/tmp/php-debugi/bin:$PATH"

Now issuing a php -v should show something like

PHP 5.6.3 (cli) (built: Oct 20 2014 19:00:00) (DEBUG)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2014 Zend Technologies

Just remember that

PHP With Debug Flags === Overhead

php Zend/bench.php
simple             0.058
simplecall         0.083
simpleucall        0.087
simpleudcall       0.088
mandel             0.145
mandel2            0.198
ackermann(7)       0.078
ary(50000)         0.014
ary2(50000)        0.015
ary3(2000)         0.123
fibo(30)           0.275
hash1(50000)       0.025
hash2(500)         0.022
heapsort(20000)    0.058
matrix(20)         0.064
nestedloop(12)     0.111
sieve(30)          0.075
strcat(200000)     0.009
Total              1.527

 php Zend/bench.php
 simple             0.234
 simplecall         0.433
 simpleucall        0.479
 simpleudcall       0.482
 mandel             0.606
 mandel2            0.896
 ackermann(7)       0.412
 ary(50000)         0.058
 ary2(50000)        0.054
 ary3(2000)         0.396
 fibo(30)           1.404
 hash1(50000)       0.088
 hash2(500)         0.099
 heapsort(20000)    0.234
 matrix(20)         0.205
 nestedloop(12)     0.355
 sieve(30)          0.247
 strcat(200000)     0.033
 Total              6.716

Hello World PHP Extension


Template to generate a configure script with phpize.

dnl config.m4 for extension hello_world

PHP_ARG_WITH(hello_world, whether to enable hello world support,
[ --enable-hello-world Enable hello world])

if test "$PHP_HELLOWORLD" != "no"; then
    PHP_NEW_EXTENSION(hello_world, hello_world.c, $ext_shared)



#include "config.h"

#include "php.h"

#define PHP_HELLO_WORLD_NAME "hello_world" /* Replace with name of your extension */
#define PHP_HELLO_WORLD_VERSION "0.1.0" /* Replace with version number for your extension */

#endif	/* PHP_HELLO_WORLD_H */


#include "php_hello_world.h"

PHP_FUNCTION (hello_world) {
    char *arg = NULL;
    size_t arg_len, len;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {

    char *greet = emalloc(strlen("Hi ") + strlen(arg) + strlen("!") + 1);
    strcpy(greet, "Hi ");
    strcat(greet, arg);
    strcat(greet, "!");

    RETURN_STRING(greet, 1);

PHP_MINIT_FUNCTION (hello_world) {

This is a simple benchmark with the CLI Sapi to appreciate the difference.

return SUCCESS; } const zend_function_entry hello_world_functions[] = { PHP_FE(hello_world, NULL) PHP_FE_END }; zend_module_entry hello_world_module_entry = { STANDARD_MODULE_HEADER, PHP_HELLO_WORLD_NAME, hello_world_functions, PHP_MINIT(hello_world), NULL, NULL, NULL, NULL, PHP_HELLO_WORLD_VERSION, STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_HELLO_WORLD ZEND_GET_MODULE(hello_world) #endif

Configure and Build Dinamically




                        php -d extension=modules/ -r 'echo hello_world("Lorenzo");'

Memory Management

(ZMM) Zend Memory Manager

  • Implicit memory cleanup at the end of each request
  • Wraps OS's memory management layer providing the e* counterparts
  • Allows persistent allocation via the p* allocators

Main Memory APIs

Prototype Description
void *emalloc(size_t size)
void *pemalloc(size_t count, char persistent);
Allocate size bytes of memory.
void *ecalloc(size_t count);
void *pecalloc(size_t count, char persistent);
Allocate a buffer for nmemb elements of size bytes and makes sure it is initialized with zeros.
void *erealloc(void *ptr, size_t count);
void *perealloc(void *ptr, size_t count, char persistent);
esize the buffer ptr, which was allocated using emalloc to hold size bytes of memory.
void efree(void *ptr);
void pefree(void *ptr, char persistent);
Free the buffer pointed by ptr. The buffer had to be allocated by emalloc.
void *estrdup(void *ptr);
void *pestrdup(void *ptr, char persistent);
Allocate a buffer that can hold the NULL-terminated string s and copy the s into that buffer.

Variables and Data Types


The fundamental unit of data storage in PHP

typedef struct _zval_struct {
    zvalue_value value;        /* variable value */
    zend_uint refcount__gc;    /* reference counter */
    zend_uchar type;           /* value type */
    zend_uchar is_ref__gc;     /* reference flag */
} zval;


The zval_value is a union which can represent all types a variable may hold:

typedef union _zvalue_value {
    long lval;
    double dval;
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;
    zend_object_value obj;
} zvalue_value;

Data Types

Native Type Constants
Constant Mapping
IS_NULL no value is set in this case
IS_LONG lval
IS_BOOL lval

Data Creation

  • There is a macro for this: MAKE_STD_ZVAL(pzv)
  • Using this macro the zval will be in an optimized chunk of memory near other zvals.
  • The macro has also the advantage to automatically handle out of memory errors and initialize refcount and is_ref properties
  • To populate a zval there's another gang of macros: ZVAL_NULL(pzv), ZVAL_BOOL(pzv, b), ZVAL_LONG(pzv, l), ZVAL_STRING(pzv, str, dup) and others

Data storage

  • Any single variable in PHP is stored in an internal array called symbol table
  • The current symbol table is called active symbol table, if the current execution is not in a function or method scope the global symbol table is the active one
  • The global scope symbol table is initialized just before the RINIT and destroyed after RSHUTDOWN
  • The main symbol table and active symbol table are defined in Zend/zend_globals.h
  • Global symbol table can be accessed with EG(symbol_table)
  • Active symbol table can be accessed with EG(active_symbol_table)

Data storage example

$foo = 'bar';
zval *foo;
ZVAL_STRING(foo, "bar", 1)
ZEND_SET_SYMBOL(EG(active_symbol_table), "foo", foo);

The new thing here is the ZEND_SET_SYMBOL(symtable, name, var) macro that exposes the foo variable to the userland via active_symbol_table

Value and type retrieval

void display_values(zval boolzv, zval *longpzv,
                zval **doubleppzv, zval *zstr)
    if (Z_TYPE(boolzv) == IS_BOOL) {
        php_printf("The value of the boolean is: %s\n",
            Z_BVAL(boolzv) ? "true" : "false");
    if (Z_TYPE_P(longpzv) == IS_LONG) {
        php_printf("The value of the long is: %ld\n",
    if (Z_TYPE_PP(doubleppzv) == IS_DOUBLE) {
        php_printf("The value of the double is: %f\n",
    if (Z_TYPE_PP(doubleppzv) == IS_DOUBLE) {
        php_printf("The value of the double is: %f\n",
    if (Z_TYPE_P(zstr) == IS_STRING) {
        php_printf("The length of the string is: %ld\n",
        php_printf("The content of the string is %s\n",



  • zend_class_entry is the internal representation of a class definition
  • Methods are defined like functions with PHP_METHOD
  • Methods are declared with the PHP_ME, PHP_MALIAS and PHP_ABSTRACT_ME macros

A sample Class

zend_class_entry *ce_Example;

zend_function_entry php_example_methods[] = {

    zend_class_entry ce_example_local;
    INIT_CLASS_ENTRY(ce_example_local, “Example”, php_example_methods);
    ce_Example = zend_register_internal_class(&ce_example_local TSRMLS_CC);
    return SUCCESS;

Add a method

PHP_METHOD(example, setName) {
   zval *obj;

    char *name;
    int name_len;

    if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &obj, ce_Example, &name, &name_len) == FAILURE) {

    zend_update_property_string(cld2_detector_ce, obj, "name", sizeof("name") - 1, name TSRMLS_CC);


Add arguments information

ZEND_BEGIN_ARG_INFO(arginfo_example_set_name, 0)
    ZEND_ARG_INFO(0, name)

Declare the method and update the MINIT

zend_function_entry php_example_methods[] = {
    PHP_ME(example, setName, arginfo_example_set_name, ZEND_ACC_PUBLIC)

    zend_class_entry ce_example_local;
    INIT_CLASS_ENTRY(ce_example_local, “Example”, php_example_methods);
    ce_Example = zend_register_internal_class(&ce_example_local TSRMLS_CC);
    zend_declare_property_string(ce_Example, "name", sizeof("name") - 1, "", ZEND_ACC_PUBLIC TSRMLS_CC);
    return SUCCESS;


Just GDB

gdb --tui --args php -d extension=modules/ -r 'echo hello_world("Lorenzo");'

Or perhaps CGDB

cgdb --args php -d extension=modules/ -r 'echo hello_world("Lorenzo");'