PHP Extension Development with PHP-CPP: Object Oriented Code
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
tocomplex.ini
and change its content toextension=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 ¶ms) {
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:
- 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 forPhp::Base
class isbase.h
.) - 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.
- 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. - 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 ¶ms);
Php::Value YourClass::example3();
Php::Value YourClass::example4(Php::Parameters ¶ms);
void YourClass::example5() const;
void YourClass::example6(Php::Parameters ¶ms) const;
Php::Value YourClass::example7() const;
Php::Value YourClass::example8(Php::Parameters ¶ms) 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:
- 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. - 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)
. - 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 ¶ms)
{
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!