Wrapping C++ Classes in a PHP Extension

One of the most common reasons people write extensions for PHP is to link against an external library and make available its API to PHP userspace scripts. Often the external library is written in C or C++. If you are tasked with writing such an extension that links against an object oriented C++ library, you will probably want to wrap the C++ classes and make them available to PHP userspace scripts. This is pretty easy to do once you get over a few initial hurdles.

In this tutorial I am going to walk you through creating a PHP extension called “vehicles” which will expose a single class called “Car” (obviously in the real-world, your extensions will expose many classes, but I’m trying to keep things simple). The extension will be built for PHP 5. I am only going to give instructions for building the extension in a UNIX-like environment, although most of what I cover should apply to Windows extension development as well. Most of what I detail here will be an expanded or simplified version of material covered in the somewhat abandoned Using C++ With PHP. I am not going to cover the basics of PHP extension writing, for that I recommend Sara Goleman’s Extension Writing series or her book Extending and Embedding PHP.

File Layout

The extension you will be creating consists of several files which you should place in a directory called vehicles:

  • car.h – Contains the definition of the C++ Car class.
  • car.cc – Contains the implementation of the C++ Car class.
  • php_vehicles.h – Contains PHP specific includes, external variables, etc.
  • vehicles.cc – The main source file of the extension. This will contain the PHP Car class.
  • config.m4 – PHP Build system / autoconf configuration file.

Configuring the PHP Build System

In order to use C++ in your PHP extension, you must instruct the PHP build system to use a C++ compiler. This is done by calling the PHP_REQUIRE_CXX() macro in your config.m4 file. PHP_REQUIRE_CXX() is defined in aclocal.m4 in the root directory of the PHP source distribution and wraps the autoconf macro AC_PROG_CXX(), which is documented here. You must also link the C++ standard library (libstdc++ on most systems), which is done with the PHP_ADD_LIBRARY() macro. Put the following in your config.m4 file:

Depending on your compiler and how you compiled PHP, you may have to wrap your includes and certain macros in a linkage specification. If you’re not sure what that means, this article explains it nicely. So, together with all the usual skeleton code for a PHP extension, your php_vehicles.h header file should look something like this:

Additionally, you’ll probably need to wrap the call to the ZEND_GET_MODULE() macro in vehicles.cc in a similar linkage specification. With that in mind, your vehicles.cc file should look like the following:

Side Note: There is a set of macros provided with the PHP / Zend API called BEGIN_EXTERN_C / END_EXTERN_C but I don’t use them. They’re defined in Zend/zend.h which is included from php.h, so they’re not available when you need them in php_vehicles.h. In my opinion, the inconsistency of using macros in some places and not in others just isn’t worth the debatable aesthetic benefit you might obtain from using them.

At this point you can do a quick sanity check and make sure that your extension is compiling properly. Possible Gotchya: Even though we haven’t done anything with it yet, make sure that car.cc exists since we referred to it in config.m4 earlier. If the file does not exist, you will run into problems with this step.

I trimmed the output, but you get the idea. You should see the extension listed when you run php -m. Also replace sudo with whatever is appropriate for your system / setup. Maybe you’re doing this as a user who has permission to write to the php extension_dir or maybe you use “su -c” or something similar.

The C++ Class

Now that you have the basic skeleton of your extension and the PHP build system knows to use a C++ compiler to compile it, you can work on the class that you’re going to expose to PHP. Notice how I’ve kept the C++ class definition and implementation separate from any extension specific code? This isn’t strictly necessary, but it decreases coupling and is generally a good idea (if you want to reuse your C++ class, you can do so without pulling over any PHP extension cruft).

car.h:

car.cc:

In order to expose this class to PHP userspace scripts, you’ll first need to define a PHP class called Car with a function_entry table that contains a method for each of the C++ class methods you wish to expose. I should point out that the PHP class does not need to have the same name as the C++ class, and it is completely feasible to include methods in your PHP class that do not exist in your C++ class and vice versa. Again, I’m striving for simplicity here so I’ve kept the class names the same and I’ve maintained a 1-to-1 mapping of C++ and PHP methods. Update vehicles.cc to contain the following:

If this is looking a little foreign to you, I recommend reading Chapters 10 and 11 of “Extending PHP”.

Connecting the Dots

At this point you have a C++ class and a PHP class with identical (or similar) definitions. Now you need to connect the two. Each instance of the PHP class must have access to exactly one instance of the C++ class. One way to do this is to use a struct to keep track of the C++ and the PHP instances. Doing so requires that you implement your own object handlers. Remember that in PHP5, an object is just a handle (a unique id that allows the Zend Engine to locate your class), a method table, and a set of handlers. You can override handlers to perform the various tasks that need to be performed at various stages of the object’s lifecycle. Add the following to the top your vehicles.cc file:

The car_object structure will be used to keep track of our C++ instances, associating them to a zend_object. Add the following functions to your vehicles.cc file, above the PHP_METHOD declarations:

And update your PHP_MINIT_FUNCTION in vehicles.cc:

There’s quite a bit going on here. First, once we have a dedicated zend_class_entry pointer in the MINIT_FUNCTION, we can assign a create_object handler to it. The create_object handler allocates memory for the object, sets up the zend_object and generates a handle for the object by storing the car_object pointer. You’ll also notice that the zend_objects_store_put function accepts a function pointer to what is basically a c-level destructor for the object being handled.

Finally, in your PHP class constructor, you’ll parse the user parameters like usual and pass them on to the C++ constructor. Once the C++ class instance is created, you can fetch the car_object pointer from the zend object store and set the car field in the struct. This makes the C++ class instance available to other instance methods of your PHP class.

Now, in order to obtain an instance of the C++ class, instance methods of your PHP class just need to call zend_object_store_get_object:

The remaining methods all work in basically the same way, I’ll leave implementing them as an exercise.

That’s it, we’re done! Now let’s take our new extension for a test drive. Don’t forget to re-compile, install and of course enable it in php.ini (or continue to use the -dextension=vehicles.so command line argument).

If you can run this script, congratulations, you’ve just created a PHP extension that wraps a C++ class.