PHP Extension Development with PHP-CPP: Object Oriented Code

Share this article

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!

Frequently Asked Questions (FAQs) about PHP Extension Development and Object-Oriented Code

What is the significance of PHP extension development in object-oriented programming?

PHP extension development plays a crucial role in object-oriented programming (OOP). It allows developers to extend the functionality of PHP by writing custom extensions in C++. These extensions can be used to optimize performance, integrate with other software, or introduce new functionality that is not available in the core PHP language. This provides a high degree of flexibility and control, enabling developers to create more efficient and powerful applications.

How does PHP-CPP work in PHP extension development?

PHP-CPP is a library for developing PHP extensions. It provides a simple and intuitive API for working with PHP variables, functions, classes, and more. With PHP-CPP, developers can write PHP extensions using the same object-oriented programming principles they use in PHP, making the development process more familiar and straightforward. This can significantly reduce the learning curve and make it easier to create high-quality extensions.

What are the benefits of using object-oriented code in PHP?

Object-oriented code in PHP offers several benefits. It promotes code reusability and modularity, making it easier to maintain and extend your codebase. It also provides a clear structure for organizing your code, which can make it easier to understand and debug. Furthermore, object-oriented programming supports encapsulation, inheritance, and polymorphism, which can help you create more flexible and robust applications.

How can I start with PHP extension development?

To start with PHP extension development, you first need to have a good understanding of both PHP and C++. You should also be familiar with the basics of object-oriented programming. Once you have these prerequisites, you can start learning about PHP extension development by reading the PHP internals documentation, following tutorials, and experimenting with simple extensions.

What are the challenges in PHP extension development?

PHP extension development can be challenging due to the complexity of the PHP internals and the C++ language. It requires a deep understanding of memory management, data structures, and low-level programming concepts. Additionally, debugging PHP extensions can be difficult because errors often result in a segmentation fault, which provides little information about the cause of the error.

Can I use PHP-CPP to develop extensions for other programming languages?

No, PHP-CPP is specifically designed for developing PHP extensions. It provides an API for interacting with the PHP engine and does not support other programming languages. If you want to develop extensions for other languages, you will need to use a different library or framework that is designed for that purpose.

How can I debug PHP extensions?

Debugging PHP extensions can be challenging, but there are tools and techniques that can help. One common approach is to use a debugger like GDB or LLDB. These tools allow you to step through your code, inspect variables, and set breakpoints, which can help you identify and fix errors. You can also use logging to output diagnostic information from your extension.

What is the role of .ini files in PHP extension development?

.ini files play a crucial role in PHP extension development. They are used to configure the PHP engine and control the behavior of your extension. For example, you can use an .ini file to enable or disable your extension, set default values for configuration options, and more. This provides a flexible and powerful way to customize the behavior of your extension.

Can I write PHP extensions in other languages besides C++?

Yes, while C++ is commonly used for PHP extension development due to its performance and flexibility, it is also possible to write PHP extensions in other languages. For example, you can write PHP extensions in C, and there are also tools and libraries that allow you to write PHP extensions in languages like Rust and Go.

How can I distribute my PHP extension?

Once you have developed a PHP extension, you can distribute it by packaging it as a PECL extension. PECL is a repository for PHP extensions, and it provides a standard way to distribute and install extensions. You can also distribute your extension as source code, which allows other developers to compile and install it themselves.

Taylor RenTaylor Ren
View Author

Taylor is a freelance web and desktop application developer living in Suzhou in Eastern China. Started from Borland development tools series (C++Builder, Delphi), published a book on InterBase, certified as Borland Expert in 2003, he shifted to web development with typical LAMP configuration. Later he started working with jQuery, Symfony, Bootstrap, Dart, etc.

CcppextensionsPHPphp extensionsphp-cpp
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week