This is the final section of a 4-part series on writing PHP extensions.
- Setting Up PHP – compiling PHP for extension development
- Hello, world! – your first extension
- Working with the API – the PHP C API
- 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_entry
s 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_value
s, 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.
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.
LikeLike
Thank you!
LikeLike
Thanks! Great tutorial, I was wondering why you haven’t posted in awhile.
LikeLike
Thank you! Yeah, writing ~5000 words plus the accompanying code repository took longer than usual 😛
LikeLike
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!!!
LikeLike
Thank you! I hope so, too!
LikeLike
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 🙂
LikeLike
Thank you! Anything in particular you’d like to learn more about?
LikeLike
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.
LikeLike
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.
LikeLike
I use joe under debian 🙂
LikeLike
You can also use Eclipse + CDT, which is, what I prefer on both platforms – Windows and Linux.
LikeLike
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 ?
LikeLike
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.
LikeLike
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/
LikeLike
could you write more about the Zend engine? How Zend parses the php scripts?
About the Core? Memory allocation ?
Thanks:)
LikeLike
Not until someone gives me a book deal for it!
I was looking for more specific questions 🙂
LikeLike
why does zend_hash_find takes zval ***(triple *** ) as a param if in it’s declaration it expects void ** ?
LikeLike
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!
LikeLike
thanks kristina1! do you have planned any other posts on php internals?
LikeLike
None planned ATM, sorry.
LikeLike
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
LikeLike
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
LikeLike
This looks great! Thank you in advance.
LikeLike
i want to create a variable in my dll, and tat variable should be accessible in php code .? how ?
LikeLike
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.
LikeLike
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.
LikeLike
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).
LikeLike
THANKS!
works.
LikeLike
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()
LikeLike
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 ?
LikeLike
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.
LikeLike
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.)
LikeLike
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.
LikeLike
Awesome, glad it worked out.
LikeLike
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
LikeLike
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?
LikeLike
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).
LikeLike
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.
LikeLike
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?
LikeLike
Glad to hear it 🙂
LikeLike
Thank you; this helped me alot.
Are you going to update it for PHP 7?
LikeLike
I don’t plan to, I’m not working with PHP anymore.
LikeLike
http://tinytechie.net/#/_posts/On_writing_php7_extension
LikeLike
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
LikeLike