Magic methods and interfaces

PHP classes have a number of magic methods that you can implement to enable special features. These are methods like __toString(), __get(), __set(), __invoke(), etcetera. With the PHP-CPP library you can implement these magic methods too.

Besides that, a core PHP installation also comes with a number of interfaces that you can implement to add even more special features to a class. These are interfaces with names like 'Countable', 'ArrayAccess' and 'Serializable'. The features that these interfaces bring, can also be implemented using PHP-CPP.

So there are magic methods and interfaces. Strangely enough, there does not seem to be any uniformity in the Zend engine in the choice between these magic methods and interfaces. To us it is unclear why some special features are implemented with magic methods, while others are activated by implementing interfaces. In our eyes the Serializable interface could just as well have been implemented with magic __serialize() and __unserialize() methods, or the __invoke() method could just as well have been an "Invokable" interface. PHP is not a standardized language, and some things just seem to be the way they are because someone felt like implementing it this way or another...

The PHP-CPP library tries to stay as close to PHP as possible. That's why in your C++ classes you can also override the magic methods and implement the special interfaces - and because C++ does not have interfaces like PHP has, we use classes with pure virtual methods instead.

Support for the SPL

A standard PHP installation comes with the Standard PHP Library (SPL). This is an extension that is built on top of the Zend engine and that uses features from the Zend engine to create classes and interfaces like Countable, Iterator and ArrayAccess.

The PHP-CPP library also has interfaces with these names, and they behave in more or less the same way as the SPL interfaces. But internally, the PHP-CPP does not depend on the SPL. If you implement a C++ interface like Php::ArrayAccess or Php::Countable, it is something different than writing a class in PHP that implements a SPL interface.

Both PHP-CPP and the SPL are directly built on top of the Zend core and offer the same sort of features, but they do not rely on each other. You can thus safely use PHP-CPP if you have not loaded the SPL extension.

The Countable interface

By implementing the Php::Countable interface, you can create objects that can be passed to the PHP count() function. You have to implement a count() method that returns the value that you want the count() function to return.


#include <phpcpp.h>

/**
 *  The famous counter class, now also implements 
 *  the Php::Countable interface
 */
class Counter : public Php::Base, public Php::Countable
{
private:
    /**
     *  The internal counter value
     *  @var int
     */
    int _value = 0;

public:
    /**
     *  C++ constructor and C++ destructpr
     */
    Counter() {}
    virtual ~Counter() {}
    
    /**
     *  Methods to increment and decrement the counter
     */
    Php::Value increment() { return ++_value; }
    Php::Value decrement() { return --_value; }

    /**
     *  Method from the Php::Countable interface, that
     *  is used when a Counter instance is passed to the
     *  PHP count() function
     *  
     *  @return long
     */
    virtual long count() override { return _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<Counter> counter("Counter");
        
        // add methods
        counter.method("increment", &Counter::increment);
        counter.method("decrement", &Counter::decrement);
        
        // add the class to the extension
        myExtension.add(std::move(counter));
        
        // return the extension
        return myExtension;
    }
}

The Counter class that we used before has been modified to show how to make classes that implement the Php::Countable interface. It is very simple, all you have to to is add the Php::Countable class as base class. This Php::Countable class has one pure virtual method, count(), that has to be implemented.

And that's is all that you have to do. There is no need to register the special count() function inside the get_module() function, adding Php::Countable as base class is sufficient.


<?php
// create a counter
$counter = new Counter();
$counter->increment();
$counter->increment();
$counter->increment();

// show the current value
echo(count($counter)."\n");
?>

The output is, as expected, the value 3.

The ArrayAccess interface

A PHP object can be turned into a variable that behaves like an array by implementing the Php::ArrayAccess interface. When you do this, objects can be accessed by with the array access operator ($object["property"]).

In the following example we use the Php::Countable and the Php::ArrayAccess interfaces to create a class that acts like an associative array than can be used for storing strings (remember: this is just an example, PHP already has support for associative arrays, so it is debatable how useful the example is).


#include <phpcpp.h>

/**
 *  A sample Map class, that can be used to map string-to-strings
 */
class Map : public Php::Base, public Php::Countable, public Php::ArrayAccess
{
private:
    /**
     *  Internally, a C++ map is used
     *  @var    std::map<std::string,std::string>
     */
    std::map<std::string,std::string> _map;

public:
    /**
     *  C++ constructor and C++ destructpr
     */
    Map() {}
    virtual ~Map() {}
    
    /**
     *  Method from the Php::Countable interface that 
     *  returns the number of elements in the map
     *  @return long
     */
    virtual long count() override 
    { 
        return _map.size(); 
    }

    /**
     *  Method from the Php::ArrayAccess interface that is
     *  called to check if a certain key exists in the map
     *  @param  key
     *  @return bool
     */
    virtual bool offsetExists(const Php::Value &key) override
    {
        return _map.find(key) != _map.end();
    }
    
    /**
     *  Set a member
     *  @param  key
     *  @param  value
     */
    virtual void offsetSet(const Php::Value &key, const Php::Value &value) override
    {
        _map[key] = value;
    }
    
    /**
     *  Retrieve a member
     *  @param  key
     *  @return value
     */
    virtual Php::Value offsetGet(const Php::Value &key) override
    {
        return _map[key];
    }
    
    /**
     *  Remove a member
     *  @param key
     */
    virtual void offsetUnset(const Php::Value &key) override
    {
        _map.erase(key);
    }
};

/**
 *  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<Map> map("Map");
        
        // add the class to the extension
        myExtension.add(std::move(map));
        
        // return the extension
        return myExtension;
    }
}

The Php::ArrayAccess has four pure virtual methods that have to be implemented. These are methods to retrieve and overwrite an element, to check if an element with a certain key exists, and a method to remove an element. In the example these methods have all been implemented to be forwarded to a regular C++ std::map object.

The Map object from the example does not have any regular methods at all. It only implements the Php::Countable interface and Php::ArrayAccess interface, so it is perfectly usable to store and retrieve properties, but it does not have any methods. The following script shows how to use it.


<?php
// create a map
$map = new Map();

// store some values
$map["a"] = 1234;
$map["b"] = "xyz";
$map["c"] = new stdClass();

// show the values
echo($map["a"]."\n");
echo($map["b"]."\n");
echo($map["c"]."\n");

// access a value that does not exist
echo($map["d"]."\n");
?>