Skip to content
moriyoshi edited this page Jul 9, 2012 · 31 revisions

Boost.PHP Basics

A typical Boost.PHP extension consists of the following source files:

  • The main C++ source file.
  • Optional source files.

Since the magic has to happen within a single compilation unit, everything related to the extension itself must go to the main source file. Other thingies may be in different units.

The main C++ source file consists of the following portions:

  • The module class definition.
  • Bootstrap macros.

An example of an empty module is shown below:

#include "boost/php/module.hpp"

using namespace boost;

// (1) Module class definition
class my_first_module
    : public php::module {
public:
    // Handler class
    class handler
        : public php::module::handler {
    public:
        handler(my_first_module* mod)
            :php::module::handler(mod) {}
    };
public:
    my_first_module(zend_module_entry* entry)
        : php::module(entry) {
    }
};

// (2) Bootstrap macros
#define BOOST_PHP_MODULE_NAME my_first_module
#define BOOST_PHP_MODULE_CAPITALIZED_NAME MY_FIRST_MODULE
#define BOOST_PHP_MODULE_VERSION "0.1"
#define BOOST_PHP_MODULE_CLASS_NAME my_first_module

#include "boost/php/module_def.hpp"

Every Boost.PHP-powered extension must contain a module class. A module class is a singleton which gets initialized right after the module is loaded. You can modify the contents of zend_module_entry within the module class’s constructor.

The module class must contain a typedef or an inner class definition for a handler. A handler is a class that has 4 methods that handle lifecycle callbacks from the PHP runtime

  • handler::__initialize()
    called when the module is being initialized
  • handler::__activate()
    called when the runtime is about to handle a new request
  • handler::__deactivate()
    called when the runtime is completing the request
  • handler::__finalize()
    called when the module is being cleaned up

The handler and the empty definitions of those methods are defined as boost::php::module::handle, so you don’t bother to write them if the module class is derived from it.

Bootstrap macros are necessary to define several functions and global variables required by PHP. There are only 4 macros that need to be defined:

BOOST_PHP_MODULE_NAME the name of the extension
BOOST_PHP_MODULE_CAPITALIZED_NAME the all-capitalized name of the extension
BOOST_PHP_MODULE_VERSION the version of the extension as a string literal
BOOST_PHP_MODULE_CLASS_NAME the name of the module class

And finally, you have to include

#include "boost/php/module_def.hpp"
to do the trick.

Adding Functions

To add functions in the empty module shown above, You need to do the following steps:

  1. Include <boost/php/function.hpp>
  2. Make the module class derive from boost::php::function_container<T> with T being the module class so that the members of the superclass will be “mixed in”.
  3. Add plain C++ functions you want to expose to PHP in the handler class.
  4. Create function entries by defun() methods giving each the function name and the pointer to the corresponding C++ function defined in the handler class, within the constructor of the module class.
  5. Setting functions member of the module entry to the function entries built above.

The following example exposes add() function which calculates the sum of the two arguments:

#include "boost/php/module.hpp"
#include "boost/php/function.hpp"

using namespace boost;

class my_first_module
    : public php::module,
      public php::function_container<my_first_module> {
public:
    class handler
        : public php::module::handler {
    public:
        handler(my_first_module* mod)
            :php::module::handler(mod) {}

        int add(int a, int b) {
            return a + b;
        }
    };
public:
    my_first_module(zend_module_entry* entry)
        : php::module(entry) {
        entry->functions = defun("add", &handler::add).
    }
};

#define BOOST_PHP_MODULE_NAME my_first_module
#define BOOST_PHP_MODULE_CAPITALIZED_NAME MY_FIRST_MODULE
#define BOOST_PHP_MODULE_VERSION "0.1"
#define BOOST_PHP_MODULE_CLASS_NAME my_first_module

#include "boost/php/module_def.hpp"

Typecasts are done automatically according to the PHP’s type-juggling rule. For example,

<?php
var_dump(add(1, 2));
var_dump(add(1, "2"));
var_dump(add("1", "2"));
var_dump(add("1", "2"));
?>

All of these invocations result in 3.

The following C++ types are allowed for function arguments:

  • Numeric types: int / long / double
  • Strings: std::string
  • Resources: boost::php::resource_handle
  • Arrays: boost::php::array
  • Variant values: boost::php::value_ptr
  • Objects: (a reference to a Boost.PHP object)

Exposing C++ classes

C++ classes can be exposed straightforward.

  1. Include <boost/php/klass.hpp>
  2. Call boost::php::def_class<T>(class name) within the handler class’s __initialize() method, where T is the class to expose. This returns a new class object that is compatible with zend_class_entry.
  3. Call defun() against the class object, with the name of the method and the pointer to the member function.
  4. Call fixup() against the class object to actually register the methods to the PHP runtime.
#include "boost/php/module.hpp"
#include "boost/php/klass.php"

using namespace boost;

class MyFirstClass {
public:
    MyFirstClass(): total_(0) {
    }

    void accumulate(int v) {
        total_ += v;
    }

    int getResult() const {
        return total_;
    }

private:
    int total_;
};

class my_first_module
    : public php::module {
public:
    class handler
        : public php::module::handler {
    public:
        handler(my_first_module* mod)
            :php::module::handler(mod) {}
        void __initialize(TSRMLS_D) {
            php::klass<MyFirstClass>& c = php::def_class<MyFirstClass>("myfirstclass" TSRMLS_CC);
            c.defun("accumulate", &MyFirstClass::accumulate);
            c.defun("getResult", &MyFirstClass::getResult);
            c.fixup();
        }
    };
public:
    my_first_module(zend_module_entry* entry)
        : php::module(entry) {}
};

#define BOOST_PHP_MODULE_NAME my_first_module
#define BOOST_PHP_MODULE_CAPITALIZED_NAME MY_FIRST_MODULE
#define BOOST_PHP_MODULE_VERSION "0.1"
#define BOOST_PHP_MODULE_CLASS_NAME my_first_module

#include "boost/php/module_def.hpp"

If the class has no default constructor, you can specify the types of the constructor arguments by a Boost.MPL vector.

...
#include <boost/mpl/vector.hpp>

using namespace boost;

class MyFirstClass {
public:
MyFirstClass(int initial): total_(initial) {
}

php::klass& c = php::def_class(“myfirstclass”, boost::mpl::vector1() TSRMLS_CC);

Using PHP functions from within the C++ code

You can also use plentiful of PHP functions in your Boost.PHP extension. This would ease the translation effort of a PHP script to C++ when the performance doesn’t matter.

To use this feature, include <boost/php/function.hpp> and simply put the following code where you want to use the PHP function:

boost::php::function var_dump("var_dump");

Where the only parameter is the name of the function.

And then, call the function just like you do to a C++ function.

var_dump(1);

The trick is that boost::php::function is a function object that takes arbitrary number of arguments.

The following example implements a function that concatenates given two strings and uppercase the resulting string through strtoupper().

#include "boost/php/module.hpp"
#include "boost/php/function.hpp"

using namespace boost;

class my_first_module
    : public php::module,
      public php::function_container<my_first_module> {
public:
    class handler
        : public php::module::handler {
    public:
        handler(my_first_module* mod)
            :php::module::handler(mod) {}

        php::value_ptr concat_and_uppercase(
                std::string a, std::string b) {
            php::function strtoupper("strtoupper");
            return strtoupper(a + b);
        }
    };
public:
    my_first_module(zend_module_entry* entry)
        : php::module(entry) {
        entry->functions =
             defun("concat_and_uppercase", &handler::concat_and_uppercase);
    }
};

#define BOOST_PHP_MODULE_NAME my_first_module
#define BOOST_PHP_MODULE_CAPITALIZED_NAME MY_FIRST_MODULE
#define BOOST_PHP_MODULE_VERSION "0.1"
#define BOOST_PHP_MODULE_CLASS_NAME my_first_module

#include "boost/php/module_def.hpp"

As you see, the script below outputs “HELLOWORLD”.

<?php
echo concat_and_uppercase("hello", "world");
?>

Returning a C++ object from an wrapped C++ function

As of the current version (3 Jul, 2009) object rvalues are finally supported. This is not feature-rich when compared to Boost.Python, but you should still find it quite useful.

How a returned object’s lifecycle is managed would be determined by the type modifier of the return value.

Type Example Behavior
reference MyFirstClass& some_function() { ... } Returning the reference without any explicit lifecycle management. This might end up with a dangling pointer in the wrapper PHP object, and thus, this feature must be used with extreme care.
pointer MyFirstClass* some_function() { ... } Returning the heap-allocated instance that will be deleted alone with the wrapper object.
smart pointer boost::shared_ptr<MyFirstClass*> some_function { ... } Returning the smart pointer.

Wrappers

Wrappers are one of the Boost.PHP’s core features. If you want to stick to the plain-old way of creating extensions for some reasons, you can still benefit from the wrappers. So, what do the wrappers do? Well, a wrapper is a thin layer of an underlying C structure such as ubiquitous zval or other commonly-seen stuff that provides an object-oriented interface that wraps a set of functions related to the structure.

  • boost::php::value (zval)
  • boost::php::value_ptr (zval *)
  • boost::php::string (stringized zval)
  • boost::php::hashtable (HashTable)
  • boost::php::object (zend_object)
  • boost::php::klass (zend_class_entry)

boost::php::value

boost::php::value is a class that directly extends zval and defines a bunch of operations that correspond to those defined in zend_operators.h, with no strings attached (read: reciprocally castable from / to zval whenever needed because it defines no additional fields.)

boost::php::value_ptr

boost::php::value_ptr is a smart-pointer class that wraps around a zval pointer. As zval pointers are essentially intrusively reference-counted containers, handling them in such a way sets you free from the reference-count nightmare, almost completely, with least performance penalty.

boost::php::string

The corresponding structure doesn’t actually exist in the PHP source code, but an anonymous structure in union zvalue_value. The class is merely a pair of a char pointer to a string buffer and an int value indicating the length of the buffer. So, semantically speaking, it appears virtually everywhere – arguments in the parameter parsing API, etc. Thus, it should often be found handy to have it as a specific type, rather than separate two variables.

boost::php::hashtable

This is a carefully-crafted wrapper of HashTable, designed to have almost the same interface as STL’s associative containers. You can use iterators in place of tiresome zend_hash_internal_pointer_reset_ex(), zend_hash_move_forward_ex(), zend_hash_get_current_data_ex(), etc. And… most notably, type safety is guaranteed. This means you no longer need to worry about which kind of pointer you should pass as the “data” parameter to the zend_hash API. boost::php::array is a convenience typedef of boost::php::hashtable<value_ptr, symtable_key> where the second template argument “symtable_key” specifies the key handling policy.

boost::php::object

Although Zend Engine’s objects API are initially designed to be neutral to any specific structure that underlies a PHP object, its primary representation of an object in C is zend_object. As zend_object requires some specific function invocations for construction and destruction, creating a wrapper and encapsulating them reduces errors and code complexity.

boost::php::klass

Class manipulation functions such as zend_register_internal_class() don’t allow much control over the zend_class_entry structure. This class doesn’t only extend zend_class_entry but provides convenience methods as well.