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.

  • 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 😛

  • 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()

  • 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()

  • Jay Patel

    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 ?

  • kristina1

    Sounds like cultist.c isn’t being linked in, are you including that when building? If you put up your code, the command you’re running, and the error message you’re getting (Github gist or similar), I can try to help debug. Or try checking out the full code at https://github.com/kchodorow/rlyeh/tree/cultists.

  • Jay Patel

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

  • Jay Patel

    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.

  • Jay Patel

    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

  • kristina1

    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?

  • kristina1

    Awesome, glad it worked out.

  • Jay Patel

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

  • kristina1

    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.

  • Elias Van Ootegem

    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?

  • kristina1

    Glad to hear it 🙂

  • DKT

    Thank you; this helped me alot.
    Are you going to update it for PHP 7?

  • kristina1

    I don’t plan to, I’m not working with PHP anymore.

  • whatvn
  • Mani Megala

    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

kristina chodorow's blog