PHP Extension Development with PHP-CPP: Object Oriented Code

Taylor Ren
This entry is part 2 of 2 in the series Developing PHP Extensions with PHP-CPP

Developing PHP Extensions with PHP-CPP

In my Part 1 on building a PHP extension with PHP-CPP, we have seen some basics in using PHP-CPP to create our first skeleton PHP extension and covered some important terminologies.

In this part, we further elaborate its OO features. We will mimic a complex number (in the form of 3+4i) class to demonstrate some more useful and powerful sides of the PHP-CPP library.

We start by modifying the empty project files provided by PHP-CPP and change the following two files to suit our new library (complex.so in this case) and my system :

  • Rename yourtextension.ini to complex.ini and change its content to extension=complex.so.
  • Modify a few lines in Makefile:
NAME                =   complex
INI_DIR             =   /etc/php5/cli/conf.d

Now we move on to create our Complex class in main.cpp.

C++ constructor and PHP constructor

In a C++ class, there are constructors and destructors. Constructors appear in a familiar form with no return type and the class name as its function name, with optional different sets of parameters (in their overloaded versions), while destructors have no return type, no parameters, share the class name prefixed with a tilt (~):

class Complex
{
    Complex();
    // We can have many other overloaded versions of the constructor 
    ~Complex();
} 

We also know that in PHP, we have some “magic” methods, in particular, __construct() and __destruct() as the class constructor and destructor.

PHP-CPP supports this kind of PHP-like magic method for constructors/destructors. This makes our implementation of a PHP extension with class easier.

(The PHP-CPP official documentation provides detailed explanations on the difference between these two “constructors/destructors”. Please read that and other related documents for more details.)

My personal view is that, as we will eventually use the class (written in C++) within the PHP script, we should implement both: C++ constructors/destructors and PHP-like magic constructors/destructors in our class. The code snippet is shown below:

class Complex : public Php::Base {
private:
    double r = 0, i = 0;

public:
    /**
     *  C++ constructor and destructor
     */
    Complex() {
    }

    virtual ~Complex() {
    }

    Php::Value getReal() {
        return r;
    }

    Php::Value getImage() {
        return i;
    }

    void __construct(Php::Parameters &params) {
        if (params.size() == 2) {
            r = params[0];
            i = params[1];
        } else {
            r=0;
            i=0;
        }
    }

In this code segment, a few things need further elaboration:

  1. Any class that is declared in our library MUST inherit from Php::Base. This is a requirement by PHP-CPP. Php::Base has some fundamental implementations of a few low-level methods for object-oriented operation. (The header file for Php::Base class is base.h.)
  2. We declared both a C++ constructor/destructor and a PHP-like magic constructor. The latter is also parameterized so that we can set the real/image part of a complex number by parameters passed in from PHP during the object instantiation.
  3. As the real/image part of a complex number defined in our class is private, we also defined two getter functions to return these two parts: getReal(), getImage(). PHP-CPP also supports properties in class. Please refer to this document for more details.
  4. Finally, as this is a fairly simple class, we do not really add in any code in the C++ constructor/destructors. All initialization work is done in the PHP-like constructor.

A member function that returns the mod of a complex number

A mod of a complex number is defined as the distance to the original point in a Descartes coordinate system. It is calculated by the below formula:

mod=sqrt(r*r+i*i)

This function is being discussed first as it is very simple: it only uses the class members for processing and returns a scalar value.

The implementation of this function is thus straightforward.

Php::Value mod() const {
    return (double)sqrt(r * r + i * i);
}

You may need to #include <cmath> for the math operations.

We will address how to register this function so that it is available to PHP later.

Like the regular function signatures we discussed in Part 1, PHP-CPP only supports 8 signatures for a member function:

// signatures of supported regular methods
void        YourClass::example1();
void        YourClass::example2(Php::Parameters &params);
Php::Value  YourClass::example3();
Php::Value  YourClass::example4(Php::Parameters &params);
void        YourClass::example5() const;
void        YourClass::example6(Php::Parameters &params) const;
Php::Value  YourClass::example7() const;
Php::Value  YourClass::example8(Php::Parameters &params) const;

Any function with a different signature than the above can still appear in the class declaration but will NOT be able to be registered and will NOT be visible to PHP script.

In this mod() function, we are using its 7th form.

A member function doing addition

Creating a function doing addition of two complex numbers is a bit trickier.

In traditional C++, we have 3 options:

  1. Override the operator + so that the addition operation on two numbers is as simple and elegant as: c+d. Unfortunately, this is not possible in PHP as PHP does not support operator overriding.
  2. A member function of this class called “add“, with another complex number as the parameter. So the addition will be done like this: c->add(d).
  3. A regular function with two parameters and returns a new complex, like this: complex_add(c, d).

Option 3 is generally considered to be the worst option and we will see how to create such an addition function using option 2.

    Php::Value add(Php::Parameters &params)
    {
        Php::Value t=params[0];
        Complex *a=(Complex *)t.implementation();

        r+=(double)a->getReal();
        i+=(double)a->getImage();

        return this;
    }``

The add function itself has two aspects of critical importance:

Firstly, please note the function signature. It uses the 4th form supported by PHP-CPP. It returns a Php::Value and accepts a parameter of type Php::Parameters, where all the parameters – in this case, there shall only be one – are passed in an array form.

Then the first line assigns the first parameter that is passed into the function (params[0]) to a Php::Value type variable t.

As we will see later in the function registration, we can be pretty sure that this variable, though assigned to Php::Value type, is actually a Complex type variable. Thus, we can safely cast that variable to a pointer to a Complex class instance. This is exactly what the 2nd line does:

Complex *a=(Complex *)t.implementation();

The implementation() method is implemented in the Php::Value class in value.h. According to the explanation of that function, which I quote below:

 /**
     *  Retrieve the original implementation
     * 
     *  This only works for classes that were implemented using PHP-CPP,
     *  it returns nullptr for all other classes
     * 
     *  @return Base*
     */ 

it does the critical task of casting a Php::Value into its underlying class implementation.

This also explains why we have to derive our own classes from Php::Base as this function returns a Php::Base pointer.

Ironically, such an important function is not explained in PHP-CPP’s current document set. I got this tip from one of the authors of PHP-CPP (Emiel, thanks!) through some email exchange.

When this implementation is in place, the add function is possible.

A magic function to display a complex number in a more friendly way

PHP-CPP supports PHP “magic methods”. We have seen __construct() in previous paragraphs and let’s further demonstrate this by creating a __toString() method in our Complex class to print the complex number in a more intuitive way, like 3+4i. (You may need to #include <sstream> for string output manipulations in the below code.)

    Php::Value __toString()
    {
        std::ostringstream os;

        os<<r;
        if(i>=0)
            os<<'+';

        os<<i<<'i';

        return os.str();
    } 

The implementation of this function is straightforward.

Registration of all the functions created so far

For this version of the mimicked Complex class, I have only implemented the above few functions. Interested users are encouraged to expand this class to add in more functionality (basic arithmetic operations, for example). The code for the Complex class and the demo in Part 1 can be cloned from Github.

To make these functions visible (callable) from a PHP script, we need to register them. Registering a class and its methods is a bit different from registering a regular function:

extern "C" {

    PHPCPP_EXPORT void *get_module() {
        // create static instance of the extension object
        static Php::Extension myExtension("complex", "1.0");

        // description of the class so that PHP knows which methods are accessible
        Php::Class<Complex> complex("Complex");

        // register the methods, and specify the parameters
        complex.method("mod", &Complex::mod, {});
        complex.method("__construct", &Complex::__construct);
        complex.method("add", &Complex::add, {
            Php::ByVal("op", "Complex", false, true)
        });

        // add the class to the extension
        myExtension.add(std::move(complex));

        // return the extension
        return myExtension;
    }
} 

In this get_module() function, we created a Php::Class variable (complex) to hold our class (Complex) information. Then we register the member functions with complex.method().

In the last complex.method() to register the add function, we specified its parameter number and type. To find out more on how to register class and declare method parameters, please refer to Parameters and Classes and objects.

Finally, we moved the complex variable into the extension and returned that extension.

Compile, Install and Test

Now, we can compile and install this complex.so extension with the following command:

make && sudo make install

If everything goes smoothly, the extension will be installed and ready to use.

To test the extension, let’s write a few lines of PHP code:

<?php

$c=new Complex(-3,-4);
echo $c->mod()."\n"; // First echo

$d=new Complex(4,3);

echo $c->add($d)->mod()."\n"; // Second echo

echo ((string)$d."\n"); // Third echo

$f=new \DateTime();
$g=$d->add($f); // Fourth echo but displays an error

The first echo will print 5, exactly what we expected.

The second echo will display 1.41421.... As we have returned a this pointer from add function, we can chain the addition operation and the mod() in one line.

The third echo displays 4+3i. This is expected as we try to stringfy a complex number using our __toString() magic method. It casts a complex number to a string using the implementation we defined.

In the fourth echo, we will encounter an error because the parameter is not of type Complex (but a DateTime object). This shows the strict type checking in PHP-CPP when an object is passed: the object type must match the registered type.

Wrapping in a namespace

Let’s wrap the class we just created into a namespace to have better encapsulation.

This process is astonishingly simple and easy. We don’t need to change the class declaration/implementation at all. All we need to do is to change the registration process in get_module() function and it now looks like this:

extern "C" {

    PHPCPP_EXPORT void *get_module() {
        // create static instance of the extension object and the namespace instance
        static Php::Extension myExtension("complex", "1.0");

        Php::Namespace myNamespace("trComplex");

        // description of the class so that PHP knows which methods are accessible
        Php::Class<Complex> complex("Complex");

        // register the methods, and specify the parameters
        complex.method("mod", &Complex::mod, {});
        complex.method("__construct", &Complex::__construct);
        complex.method("add", &Complex::add, {
            Php::ByVal("op", "trComplex\\Complex", false, true)
        });

        // add the class to namespace
        myNamespace.add(std::move(complex));

        // add the namespace to the extension
        myExtension.add(std::move(myNamespace));

        // return the extension
        return myExtension;
    }

Instead of adding the class directly into the extension, we added one more namespace layer (named trComplex in this case). The class and its member registration remains almost identical except that in add function’s parameter declaration, we used trComplex\\Complex instead of Complex. Next, we moved the class into the namespace, and the namespace into the extension.

To test the new namespace and class, we can modify the testing snippets:

<?php

$c=new trComplex\Complex(-3,-4);
echo $c->mod()."\n";

$d=new trComplex\Complex(4,3);

echo $c->add($d)->mod()."\n";

echo ((string)$d."\n");

$f=new \DateTime();
$g=$d->add($f); //Error!

Instead of just using Complex, we now use trComplex\Complex (the fully qualified name of this class) to declare the variables. The output will be the same as the previous demo. It shows that a PHP-CPP namespace is identical to a natural PHP namespace.

Final thoughts

PHP-CPP is very powerful and easy to use. I do hope that after these two parts of discussion, users will find it a promising library to help develop PHP extensions using familiar C++ syntax and semantics.

There are a few areas for improvement, though.

First of all, its documentation needs more insightful API explanations. For example, we see earlier that the implementation() method is not covered in its current online documentation. This should not be the case. Users need step-by-step instructions and demos (which are now in its official documentation), but also need a full API reference.

Secondly, we do hope its 1.0 major release will come as soon as possible. In my testing, some funny segmentation errors popped up and after upgrading to the latest version, these errors disappeared; but we do need a stable 1.0 version.

Third, even though in the last echo in our demo, PHP prompts: “PHP Catchable fatal error: Argument 1 passed to Complex::add() must be an instance of Complex, instance of DateTime given…”, this error is not catchable in a try...catch block in PHP. This is a bit annoying.

In this Part 2 on PHP-CPP, we covered several important aspects: class and member functions, magic methods, namespace encapsulation, etc.

I would recommend PHP-CPP as the development tool to develop PHP extensions. Its advantages are obvious: fast learning curve, familiar C++ syntax, PHP magic methods support, OO features, etc.

Feel free to comment and let us know your thoughts!

Developing PHP Extensions with PHP-CPP

<< Getting Started with PHP Extension Development via PHP-CPP

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://www.copernica.com Emiel Bruijntjes

    Thank you for writing another article about PHP-CPP, and for giving us some suggestions to improve the library.

    I’m happy to say that we have recently released version 1.0 of the PHP-CPP library. This is the first stable release that we ourselves actually use in production. This will not be the last release however, so always keep an eye on the website for newer, faster, more powerful and maybe more stable releases. We constantly improve the library based on feedback like yours.

    You are right that the implementation() method was missing from the documentation. Thank you for pointing that out. I’ve therefore just updated the website to include a paragraph about this method: http://www.php-cpp.com/documentation/variables#objects. You are also right about the lack of a good reference manual. We have many articles on http://www.php-cpp.com/documentation that explain how to use the library, and we did our best to cover all topics and methods in it, but we indeed do not yet a function-by-function and method-by-method reference manual. This is something that should be added in the future.

    Your final remark about the non-catchable fatal error is not a problem caused by the PHP-CPP library. This error comes from the internal PHP/Zend engine. You would run into this very same error had you implemented your ComplexNumber class completely in PHP. If you would like to see this different, you will have to address this to Zend, and not to PHP-CPP.

    Once again, thanks for your article, and keep spreading the word :)

    • http://www.bitfalls.com/ Bruno Skvorc

      Thank you for the feedback!

    • Taylor Ren

      Good to hear the 1.0 is out and running. Congratulations and keep rolling.

    • Taylor Ren

      I think you have fixed the uncatchable error in your latest release, right?

      • http://www.copernica.com Emiel Bruijntjes

        It was not broken :) But I understand the confusion. When you see from a PHP script an error like “PHP Catchable fatal error” – that error has nothing to do with exceptions and try-catch blocks (although you might expect that because of the word “Catchable”).

        The error message means that a fatal error has occured that causes your script to crash, but that the error is of a type that could have been handled by your own custom error handler, if you had set one with the PHP function set_error_handler().

        In my opinion the message “PHP Catchable fatal error” is confusing because it indeed suggests that it has something to do with try-catch blocks and exceptions, which it doesn’t. It was better if they had not used the word “Catchable”. The message comes from the Zend engine internally, so it is not something that we can fix in the PHP-CPP library.

        What we did recently change in PHP-CPP is this: for many error that occured we used to throw a PHP exception. This is however not in line with the Zend engine, which in most cases triggers a fatal error (which is something different than throwing an exception). We have changed this so that PHP-CPP now also triggers fatal errors when you do something wrong, to be more compatible with Zend. (Although in my personal opinion throwing an exception is much more logical than the PHP fatal error system – but we’d like to follow the PHP standards in this).