One of the questions we had to ask ourselves when we developed the PHP-CPP library was whether we should follow PHP conventions or follow C++ conventions for many of the library features.

In PHP scripts you can use magic methods and magic interfaces to add special behavior to classes. With C++ classes you can achieve the same, but by using technologies like operator overloading, implicit constructors and casting operators. The PHP __invoke() method for example, is more or less identical to operator () in C++. The question that we asked ourselves was whether we should automatically pass the __invoke PHP method to a C++ operator() call - or use the same __invoke() method name in C++ too?

We have decided to follow the PHP conventions, and use magic methods and magic interfaces in C++ as well - although we must admit that having methods that start with two underscores does not make the code look very pretty. But by using magic methods the switch from PHP to C++ is kept simpler for starting C++ programmers. And on top of that, not all magic methods and interfaces could have been implemented with core C++ features (like operator overloading), so we had to use some magic methods and/or interfaces anyway. That's why we decided that because we had to use some magic methods in C++, we could just as well follow PHP completely and support all magic PHP methods in C++ too.

Besides the magic methods and interfaces that are available in PHP user space, the Zend engine has additional features that are not exposed to PHP user space scripts. Features that are only accessible for extension programmers. The PHP-CPP library also supports these special features. This means that if you use PHP-CPP for writing functions and classes, you can achieve things that can not be achieved by writing pure PHP code.

Extra casting functions

Internally, the Zend engine has special casting routines to cast objects to integers, to booleans and to floating point values. For one reason or another, a PHP script can only implement the __toString() method, while all other casting operations are kept away from it. The PHP-CPP library solves this limitation, and allows one to implement the other casting functions as well.

One of the design goals of the PHP-CPP library is to stay as close to PHP as possible. For that reason the casting functions have been given names that match the __toString() method: __toInteger(), __toFloat(), and __toBool().

#include <phpcpp.h>

/**
 *  A sample class, with methods to cast objects to scalars
 */
class MyClass : public Php::Base
{
public:
    /**
     *  C++ constructor and C++ destructpr
     */
    MyClass() = default;
    virtual ~MyClass() = default;

    /**
     *  Cast to a string
     *
     *  Note that now we use const char* as return value, and not Php::Value.
     *  The __toString function is detected at compile time, and it does
     *  not have a fixed signature. You can return any value that can be picked
     *  up by a Php::Value object.
     *
     *  @return const char *
     */
    const char *__toString()
    {
        return "abcd";
    }

    /**
     *  Cast to a integer
     *  @return long
     */
    long __toInteger()
    {
        return 1234;
    }

    /**
     *  Cast to a floating point number
     *  @return double
     */
    double __toFloat()
    {
        return 88.88;
    }

    /**
     *  Cast to a boolean
     *  @return bool
     */
    bool __toBool()
    {
        return true;
    }
};

/**
 *  Switch to C context to ensure that the get_module() function
 *  is callable by C programs (which the Zend engine is)
 */
extern "C" {
    /**
     *  Startup function that is called by the Zend engine
     *  to retrieve all information about the extension
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() {

        // extension object
        static Php::Extension myExtension("my_extension", "1.0");

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

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

        // return the extension
        return myExtension;
    }
}

The casting methods are automatically called when an object is casted to a scalar type, or when it is used in a scalar context. The following example demonstrates this.

<?php
// initialize an object
$object = new MyClass();

// cast it
echo((string)$object."\n");
echo((int)$object."\n");
echo((bool)$object."\n");
echo((float)$object."\n");

?>

Comparing objects

If you compare two objects in PHP with comparison operators like <, ==, !=, > (and the obvious others), the Zend engine runs an object comparison function. The PHP-CPP library intercepts this method, and passes the comparison method to the __compare method of your class. In other words, if you want to install a custom comparison operator, you can do so by implementing __compare().

#include <phpcpp.h>
/**
 *  A sample class, that shows how objects can be compared
 */
class MyClass : public Php::Base
{
private:
    /**
     *  Internal value of the class
     *  @var    int
     */
    int _value;

public:
    /**
     *  C++ constructor
     */
    MyClass()
    {
        // start with random value
        _value = rand();
    }

    /**
     *  C++ destructor
     */
    virtual ~MyClass() = default;

    /**
     *  Cast the object to a string
     *  @return std::string
     */
    std::string __toString()
    {
        return std::to_string(_value);
    }

    /**
     *  Compare with a different object
     *  @param  that
     *  @return int
     */
    int __compare(const MyClass &that) const
    {
        return _value - that._value;
    }
};

/**
 *  Switch to C context to ensure that the get_module() function
 *  is callable by C programs (which the Zend engine is)
 */
extern "C" {
    /**
     *  Startup function that is called by the Zend engine
     *  to retrieve all information about the extension
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() {

        // extension object
        static Php::Extension myExtension("my_extension", "1.0");

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

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

        // return the extension
        return myExtension;
    }
}

The comparison function is automatically called when you try to compare objects in PHP scripts. It should return 0 when the two objects are identical, a value less than zero when the 'this' object is smaller, and higher than zero when 'this' is bigger.

<?php
// initialize a couple of objects
$object1 = new MyClass();
$object2 = new MyClass();
$object3 = new MyClass();

// compare the objects
if ($object1 < $object2)
{
    echo("$object1 is smaller than $object2\n");
}
else
{
    echo("$object1 is bigger than $object2\n");
}

if ($object1 == $object3)
{
    echo("$object1 is equal to $object3\n");
}
else
{
    echo("$object1 is not equal to $object3\n");
}
?>

The above PHP script could produce the following output:

// output
1699622247 is bigger than 151717746
1699622247 is not equal to 627198306