PHP Extensions Made Eldrich: Classes

This is the final section of a 4-part series on writing PHP extensions.

  1. Setting Up PHP – compiling PHP for extension development
  2. Hello, world! – your first extension
  3. Working with the API – the PHP C API
  4. Classes – creating PHP objects in C
Objects

branch: oop

This section will cover creating objects. Objects are like associative arrays++, they allow you to attach almost any functionality you want to a PHP variable.

You can create an object in much the same way that you’d create an array:

PHP_FUNCTION(makeObject) {
    object_init(return_value);

    // add a couple of properties
    zend_update_property_string(NULL, return_value, "name", strlen("name"), "yig" TSRMLS_CC);
    zend_update_property_long(NULL, return_value, "worshippers", strlen("worshippers"), 4 TSRMLS_CC);
}

If you call var_dump(makeObject()), you’ll see something like:

object(stdClass)#1 (2) {
  ["name"]=>
  string(3) "yig"
  ["worshippers"]=>
  int(4)
}

Classes

branch: cultists

You create a class by designing a class template, stored in a zend_class_entry.

For our extension, we’ll make a new class, Cultist. We want a standard cultist template, but every individual cultist is unique.

I like to give each class its own C file to keep things tidy, but that’s not necessary if it’s more logical to group them together or something. However, it’s my tutorial, so we’re splitting it out.

Add two new files to your extension directory: cultist.c and cultist.h. Add the new C file to your config.m4, so it will get compiled into your extension:

PHP_NEW_EXTENSION(rlyeh, php_rlyeh.c cultist.c, $ext_shared)

Note that there is no comma between php_rlyeh.c and cultist.c.

Now we want to add our Cultist class. Open up cultist.c and add the following code:

#include <php.h>
 
#include "cultist.h"
 
zend_class_entry *rlyeh_ce_cultist;
 
static function_entry cultist_methods[] = {
  PHP_ME(Cultist, sacrifice, NULL, ZEND_ACC_PUBLIC)
  {NULL, NULL, NULL}
};
 
void rlyeh_init_cultist(TSRMLS_D) {
  zend_class_entry ce;
 
  INIT_CLASS_ENTRY(ce, "Cultist", cultist_methods);
  rlyeh_ce_cultist = zend_register_internal_class(&ce TSRMLS_CC);
 
  /* fields */
  zend_declare_property_bool(rlyeh_ce_cultist, "alive", strlen("alive"), 1, ZEND_ACC_PUBLIC TSRMLS_CC);
}
 
PHP_METHOD(Cultist, sacrifice) {
  // TODO                                                                                                                                                                   
}

You might recognize the function_entry struct from our original extension: methods are just grouped into function_entrys per class.

The real meat-and-potatoes is in rlyeh_init_cultist. This function defines the class entry for cultist, giving it methods (cultist_methods), constants, and properties.

There are tons of flags that can be set for methods and properties. Some of the most common are:

ZEND_ACC_STATIC
ZEND_ACC_PUBLIC
ZEND_ACC_PROTECTED
ZEND_ACC_PRIVATE
ZEND_ACC_CTOR
ZEND_ACC_DTOR
ZEND_ACC_DEPRECATED

Currently we’re just using ZEND_ACC_PUBLIC for our sacrifice function, but this could be OR-ed with any of the other flags (for example, if we decided sacrifice2() had a better API, we could change sacrifice‘s flags to ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED and PHP would warn the user if they tried to use it).

In cultist.h, define all of the functions used above:

#ifndef CULTIST_H
#define CULTIST_H
 
void rlyeh_init_cultist(TSRMLS_D);
 
PHP_METHOD(Cultist, sacrifice);
 
#endif

Now we have to tell the extension to load this class on startup. Thus, we want to call rlyeh_init_cultist in our MINIT function and include the cultist.h header file. Open up php_rlyeh.c and add the following:

// at the top
#include "cultist.h"
 
// our existing MINIT function from part 3
PHP_MINIT_FUNCTION(rlyeh) {
  rlyeh_init_cultist(TSRMLS_C);
}

Because we changed config.m4, we have to do phpize && ./configure && make install, not just make install, otherwise cultist.c won’t be added to the Makefile.

Now if we run var_dump(new Cultist());, we will see something like:

object(Cultist)#1 (1) {
  ["alive"]=>
  bool(true)
}
Creating a new class instance

We can also initialize cultists from C. Let’s add a static function to create a cultist. Open cultist.c and add the following:

static function_entry cultist_methods[] = {
  PHP_ME(Cultist, sacrifice, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Cultist, createCultist, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  {NULL, NULL, NULL}
};
 
PHP_METHOD(Cultist, createCultist) {
   object_init_ex(return_value, rlyeh_ce_cultist);
}

Now we can call Cultist::createCultist() to create a new cultist.

What if creating new cultists takes some setup, so we’d like to have a constructor? Well, the constructor is just a method, so we can add that:

static function_entry cultist_methods[] = {
  PHP_ME(Cultist, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
  PHP_ME(Cultist, sacrifice, NULL, ZEND_ACC_PUBLIC)
  PHP_ME(Cultist, createCultist, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
  {NULL, NULL, NULL}
};
 
PHP_METHOD(Cultist, __construct) {
  // do setup
}

Now PHP will automatically call our Cultist::__construct when we call new Cultist. However, createCultist won’t: it’ll just set the defaults and return. We have to modify createCultist to call a PHP method from C.

Calling method-to-method

branch: m2m

First, add this enormous block to your php_rlyeh.h file:

#define PUSH_PARAM(arg) zend_vm_stack_push(arg TSRMLS_CC)
#define POP_PARAM() (void)zend_vm_stack_pop(TSRMLS_C)
#define PUSH_EO_PARAM()
#define POP_EO_PARAM()
 
#define CALL_METHOD_BASE(classname, name) zim_##classname##_##name
 
#define CALL_METHOD_HELPER(classname, name, retval, thisptr, num, param) \
  PUSH_PARAM(param); PUSH_PARAM((void*)num);                            \
  PUSH_EO_PARAM();                                                      \
  CALL_METHOD_BASE(classname, name)(num, retval, NULL, thisptr, 0 TSRMLS_CC); \
  POP_EO_PARAM();                       \
  POP_PARAM(); POP_PARAM();
 
#define CALL_METHOD(classname, name, retval, thisptr)                  \
  CALL_METHOD_BASE(classname, name)(0, retval, NULL, thisptr, 0 TSRMLS_CC);
 
#define CALL_METHOD1(classname, name, retval, thisptr, param1)         \
  CALL_METHOD_HELPER(classname, name, retval, thisptr, 1, param1);
 
#define CALL_METHOD2(classname, name, retval, thisptr, param1, param2) \
  PUSH_PARAM(param1);                                                   \
  CALL_METHOD_HELPER(classname, name, retval, thisptr, 2, param2);     \
  POP_PARAM();
 
#define CALL_METHOD3(classname, name, retval, thisptr, param1, param2, param3) \
  PUSH_PARAM(param1); PUSH_PARAM(param2);                               \
  CALL_METHOD_HELPER(classname, name, retval, thisptr, 3, param3);     \
  POP_PARAM(); POP_PARAM();

These macros let you call PHP functions from C.

Add the following to cultist.c:

#include "php_rlyeh.h"

PHP_METHOD(Cultist, createCultist) {
  object_init_ex(return_value, rlyeh_ce_cultist);
  CALL_METHOD(Cultist, __construct, return_value, return_value);
}

this

branch: this

We’ve pretty much just been dealing with return_values, but now that we’re working with objects we can also access this. To get this, use the getThis() macro.

For example, suppose we want to set a couple of properties in the constructor:

PHP_METHOD(Cultist, __construct) {
  char *name;
  int name_len;
  // defaults                                                                                                                                                               
  long health = 10, sanity = 4;
 
  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ll", &name, &name_len, &health, &sanity) == FAILURE) {
    return;
  }
 
  zend_update_property_stringl(rlyeh_ce_cultist, getThis(), "name", strlen("name"), name, name_len TSRMLS_CC);
  zend_update_property_long(rlyeh_ce_cultist, getThis(), "health", strlen("health"), health TSRMLS_CC);
  zend_update_property_long(rlyeh_ce_cultist, getThis(), "sanity", strlen("sanity"), sanity TSRMLS_CC);
}

Note the zend_parse_parameters argument: “s|ll”. The pipe character (“|”) means, “every argument after this is optional.” Thus, at least 1 argument is required (in this case, the cultist’s name), but health and sanity are optional.

Now, if we create a new cultist, we get something like:

$ php -r 'var_dump(new Cultist("Todd"));'
object(Cultist)#1 (4) {
  ["alive"]=>
  bool(true)
  ["name"]=>
  string(4) "Todd"
  ["health"]=>
  int(10)
  ["sanity"]=>
  int(4)
}
Attaching Structs

As mentioned earlier, you can attach a struct to an object. This lets the object carry around some information that is invisible to PHP, but usable to your extension.

You have to set up the zend_class_entry in a special way when you create it. First, add the struct to your cultist.h file, as well as the extra function declaration we’ll be using:

typedef struct _cult_secrets {
    // required
    zend_object std;
 
    // actual struct contents
    int end_of_world;
    char *prayer;
} cult_secrets;
 
zend_object_value create_cult_secrets(zend_class_entry *class_type TSRMLS_DC);
void free_cult_secrets(void *object TSRMLS_DC);
// existing init function
void rlyeh_init_cultist(TSRMLS_D) {
  zend_class_entry ce;
 
  INIT_CLASS_ENTRY(ce, "Cultist", cultist_methods);
  // new line!
  ce.create_object = create_cult_secrets;
  rlyeh_ce_cultist = zend_register_internal_class(&ce TSRMLS_CC);
 
  /* fields */
  zend_declare_property_bool(rlyeh_ce_cultist, "alive", strlen("alive"), 1, ZEND_ACC_PUBLIC TSRMLS_CC);
}
 
zend_object_value create_cult_secrets(zend_class_entry *class_type TSRMLS_DC) {
  zend_object_value retval;
  cult_secrets *intern;
  zval *tmp;
 
  // allocate the struct we're going to use
  intern = (cult_secrets*)emalloc(sizeof(cult_secrets));
  memset(intern, 0, sizeof(cult_secrets));
 
  // create a table for class properties
  zend_object_std_init(&intern->std, class_type TSRMLS_CC);
  zend_hash_copy(intern->std.properties,
     &class_type->default_properties,
     (copy_ctor_func_t) zval_add_ref,
     (void *) &tmp,
     sizeof(zval *));
 
  // create a destructor for this struct
  retval.handle = zend_objects_store_put(intern, (zend_objects_store_dtor_t) zend_objects_destroy_object, free_cult_secrets, NULL TSRMLS_CC);
  retval.handlers = zend_get_std_object_handlers();
 
  return retval;
}
 
// this will be called when a Cultist goes out of scope
void free_cult_secrets(void *object TSRMLS_DC) {
  cult_secrets *secrets = (cult_secrets*)object;
  if (secrets->prayer) {
    efree(secrets->prayer);
  }
  efree(secrets);
}

If we want to access this, we can fetch the struct from getThis() with something like:

PHP_METHOD(Cultist, getDoomsday) {
  cult_secrets *secrets;
 
  secrets = (cult_secrets*)zend_object_store_get_object(getThis() TSRMLS_CC);
 
  RETURN_LONG(secrets->end_of_world);
}

Exceptions

branch: exceptions

All exceptions must descend from the base PHP Exception class, so this is also an intro to class inheritance.

Aside from extending Exception, custom exceptions are just normal classes. So, to create a new one, open up php_rlyeh.c and add the following:

// include exceptions header
#include <zend_exceptions.h>
 
zend_class_entry *rlyeh_ce_exception;
 
void rlyeh_init_exception(TSRMLS_D) {
  zend_class_entry e;
 
  INIT_CLASS_ENTRY(e, "MadnessException", NULL);
  rlyeh_ce_exception = zend_register_internal_class_ex(&e, (zend_class_entry*)zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC);
}
 
PHP_MINIT_FUNCTION(rlyeh) {
  rlyeh_init_exception(TSRMLS_C);
}

Don’t forget to declare rlyeh_init_exception in php_rlyeh.h.

Note that we could add our own methods to MadnessException with the third argument to INIT_CLASS_ENTRY, but we’ll just leave it with the default exception methods it inherits from Exception.

Throwing Exceptions

An exception isn’t much good unless we can throw it. Let’s add a method that can throw it:

zend_function_entry rlyeh_functions[] = {
  PHP_FE(cthulhu, NULL)
  PHP_FE(lookAtMonster, NULL)
  { NULL, NULL, NULL }
};
 
PHP_FUNCTION(lookAtMonster) {
  zend_throw_exception(rlyeh_ce_exception, "looked at the monster too long", 1000 TSRMLS_CC);
}

The 1000 is the exception code, you can set that to whatever you want (users can access it from the exception with the getCode() method).

Now, if we compile and install, we can run lookAtMonster() and we’ll get:

Fatal error: Uncaught exception 'MadnessException' with message 'looked at the monster too long' in Command line code:1
Stack trace:
#0 Command line code(1): lookAtMonster()
#1 {main}
  thrown in Command line code on line 1

Congratulations, now you’ve stared into the abyss!

This tutorial is an ongoing work. I hope you’ve enjoyed it and please comment below if you think I’ve missed any important topics or anything is unclear.

  • http://twitter.com/ppadron Pedro Padron

    by far the best php extensions tutorial around. the “Attaching Structs” section is the missing part of all other articles I’ve read, and yet it’s the most useful.

  • Anonymous

    Thank you!

  • BRETT LANGDON

    Thanks! Great tutorial, I was wondering why you haven’t posted in awhile.

  • Anonymous

    Thank you! Yeah, writing ~5000 words plus the accompanying code repository took longer than usual :-P

  • Rune Kaagaard

    Wauw, this is really groundbreaking in the history of the PHP internal documentation. Hopefully this will get a lot more to cross over from PHP user to PHP developer. Nicely done!!!

  • Tomasz Budzyński

    i must say this is the best tutorial I’ve found about PHP extensions. Thank You very much for writing it and I’m waiting for more :)

  • Anonymous

    Thank you! I hope so, too!

  • Anonymous

    Thank you!  Anything in particular you’d like to learn more about?

  • Pingback: Programowanie w PHP » Blog Archive » Kristina Chodorow’s Blog: Writing a PHP Extension (Four Part Series)

  • Tomasz Budzyński

    I don’t know if there is something that is not describe in Your articles. Maybe resource, but i don’t know if it is so important.Now i must understand C, C++ is much simpler with all the frameworks ect.
    One question, is there any IDE (on linux) that help to write extension ? I’ve try netbeans but I’ve had problem to create dynamic or static library. So I’m using kate now, but is is not so friendly as netbeans.

  • Anonymous

    I always use Emacs, but it’s not exactly friendly :)

    I’ve heard Visual Studio is the best IDE for C/C++ development.  If you’re on Windows, you could try the Express version of that.  Otherwise… most people end up on either Vi or Emacs.

  • Tomasz Budzyński

    After a few days of writing php extension (I’m just rewriting my little php class to extension) and i ran on passing variable as reference in php method. Is it supported by zend engine or is there some special thing that i should do.
    Another thing that I’m wondering is that my code usually generate php code that is some sort of cache that is used. Any hint how to replace it in php extension written in C ?

  • Anonymous

    Anything you can do in PHP you can do in a C extension, because the Zend engine *is* PHP.

    You can pass arguments by reference.  I’ll write up a quick follow-up post on that.

  • Anonymous

    I’ve written a short post on how to specify references in C: http://www.snailinaturtleneck.com/blog/2011/09/07/more-php-internals-references/

  • Greubel

    Thank you for these great series of writing a good php extension. Just a question: for my personal extension I need to check in a PHP_FUNCTION or a PHP_METHOD the object type of a given argument. In case of unexcepted objects I want to trigger an error message to user. It should go up something like that (php code):

    function foo( $val )
    {
      if( ! is_a($val, ‘ClassTypeHere’) )
        throw new BadArgumentException();
    }

    I know that an argument can be defined type-safe, but inside of an extension? Is it possible to do this, and if so, how?

    Cheers,
    Maik

  • Anonymous

    You’re welcome!

    Check out the ‘O’ option for zend_parse_parameters.  You pass in the address of a zval* and the class entry of object you want it to be:

    zval *obj = 0;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “O”, &obj, some_ce) == FAILURE) {
        return;
    }

    zend_parse_params will take care of issuing the PHP warning for sending the wrong type.  

    Here is an example from the MongoDB PHP driver: a constructor requires that the first argument be a MongoDB (mongo_ce_DB):  https://github.com/mongodb/mongo-php-driver/blob/master/collection.c#L70

  • Greubel

    This looks great! Thank you in advance.

  • Shabeer Ali M

    i want to create a variable in my dll, and tat variable should be accessible in php code .? how ?

  • Anonymous

    I have never done this before, but it seems like it might be possible to do it by injecting it into the global scope.  I’m not sure how, you’d have to dig through the PHP API. 

    You might want to consider making it a member of a class (e.g., MyClass::$someGlobal instead of $someGlobal), which is covered in the “Classes” section of this tutorial.

  • am

    could you write more about the Zend engine? How Zend parses the php scripts?
    About the Core? Memory allocation ?
    Thanks:)

  • Anonymous

    Not until someone gives me a book deal for it!

    I was looking for more specific questions :)

  • am

    why does zend_hash_find takes zval ***(triple *** ) as a param if in it’s declaration it expects void ** ?

  • Anonymous

    You technically don’t _have_ to store zvals in the HashTable struct, you can store any pointer type you’d like (hence void instead of zval).  IIRC, you also don’t have to send it a *** if you’re managing the memory yourself.  The extra layer of indirection is only for user-facing hashes (associative arrays) and *** is how PHP passes zvals around (I don’t know why that is).  If a hashtable is never cleaned up by user garbage collection, you can actually store a ** of any type, instead of a ***.  You just have to remember what the heck is in there, so you can clean it up!

  • am

    thanks kristina1! do you have planned any other posts on php internals?

  • Anonymous

    None planned ATM, sorry.

  • Itay Malimovka

    If I have a method inside an Object, how would I make this method emulate the php’s return $this.
    i.e. a call to this function will return a reference to the php object containing the method.

  • kristina1

    Something like:

    PHP_METHOD(MyClass, someMethod) {
        /* do stuff */

    RETURN_ZVAL(getThis(), 1, 0);
    }

    See https://github.com/mongodb/mongo-php-driver/blob/master/cursor.c for lots of examples (matches up with this class: http://php.net/manual/en/class.mongocursor.php, which has lots of methods that return the instance itself).

  • Itay Malimovka

     THANKS!
    works.

  • Zsombor Kaló

    I use joe under debian :)

  • Greubel

     You can also use Eclipse + CDT, which is, what I prefer on both platforms – Windows and Linux.

  • Pingback: PHP扩展开发笔记 – MoXie's blog

  • http://www.facebook.com/profile.php?id=100000887316947 Jaco Ruit

    Make sure to call  zend_object_std_dtor() in free_cult_secrets() like:

    zend_object_std_dtor(&secrets->std TSRMLS_CC);

    This function frees the properties hashtable allocated by zend_object_std_init()

kristina chodorow's blog