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 

#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_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.

47 thoughts on “PHP Extensions Made Eldrich: Classes

  1. 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!!!

    Like

  2. 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 🙂

    Like

      1. 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.

        Like

      2. 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.

        Like

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

        Like

      4. 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 ?

        Like

      5. 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.

        Like

      6. 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/

        Like

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

        Like

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

        Like

      9. 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!

        Like

  3. 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

    Like

    1. 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

      Like

    1. 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.

      Like

  4. 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.

    Like

  5. 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()

    Like

  6. I tried to follow the steps on cultist branch, however, I am getting a undefined reference to “ryleh_init_cultist”. I added the “cultist.h” file to hello.c, and I added the method declaration too. Dont know why it is not linking them ?

    Like

      1. Hey Kristina, Thanks for the quick response.

        I accidently used static zend_function_entry cultist_methods[] instead of static function_entry cultist_methods[], and it was somehow moving further. But when I replaced it, I am seeing this exception :

        /root/php-5.4.21/ext/hello/cultist.c:7: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘cultist_methods’
        /root/php-5.4.21/ext/hello/cultist.c: In function ‘rlyeh_init_cultist’:
        /root/php-5.4.21/ext/hello/cultist.c:16: error: ‘cultist_methods’ undeclared (first use in this function)
        /root/php-5.4.21/ext/hello/cultist.c:16: error: (Each undeclared identifier is reported only once
        /root/php-5.4.21/ext/hello/cultist.c:16: error: for each function it appears in.)

        Like

      2. Hey Kristina,

        Got it to work. Btw, I had to create the extension from scratch. I was following multiple tutorials, and probably because of that, had some trouble.

        However, I create an extension using this command :

        ./ext_skel –extame=hello

        This is really a very nice way to get started. Helped me to get a quick start. Not sure if it would be nice to add this to your blog. Love your blogs.

        Like

  7. Had an exception on branch “struct”.

    “zend_class_entry has no member named default_properties”.

    Resolved it using this patch:
    #if PHP_VERSION_ID std.properties, &class_type->default_properties, (copy_ctor_func_t) zval_add_ref, (void *) &tmp, sizeof(zval *));
    #else
    object_properties_init((zend_object*) &(intern->std), class_type);
    #endif

    Like

    1. Oh, interesting, I’m not sure PHP 5 was even around when I wrote this, the API must have changed. Could you send a pull request on Github to update the file?

      Like

  8. Hey, when I tried copying the .so file I created by make and make install on extension folder, to a seperate machine, with exact same configuration, I am getting this error :

    PHP Warning: PHP Startup: Unable to load dynamic library ‘/usr/lib64/php/modules/rlyeh.so’ – /usr/lib64/php/modules/rlyeh.so: undefined symbol: rlyeh_init_cultist in Unknown on line 0

    Can you help me figure out why could that be an issue? I am using PHP5.4 and using CentOS release 6.4 (Final).

    Like

    1. Does it work on the original machine? If so, it’s probably a mismatch in PHP versions: the PHP used to build the extension might be 64-bit PHP and the PHP used by the Apache/Nginx module might be 32-bit PHP.

      Like

  9. Never mind, I just answered my own question: No, it’s not required to call the constructor explicitly, you can just get the struct from the new instance (return_value) and set the members accordingly (see code snippet below).

    Hi, a quick question about the `CALL_METHOD` exampe you listed. I’m working on an extension where a method returns an instance of another class. I currently inti the `return_value`, then pass `getThis()` along with some other values to the new objects’ constructor.

    Do I really need to do this, because I’m actually initializing an internal struct in the constructor I’m calling. Is it possible to do something like this:

    object_init_ex(return_value, my_ext_class_ce);
    struct _internal_t *intern_new = (struct _internal_t *) zend_object_store_get_object(return_value TSRMLS_CC);
    struct _other_internal_t *intern_this = (struct _other_internal_t *) zend_object_store_get_object(getThis() TSRMLS_CC);
    intern_new->foo = intern_this->bar;

    Or do I have to do this in the constructor of the new object I’m creating?

    Like

  10. First of all thank you for sharing this informative blog.. This concept explanation are very easy and step by step so easy to understand and also learnt this concept clearly from this article..

    php course in chennai

    Like

Leave a comment