Magic methods

Every PHP class has "magic methods". You may already know these methods from writing PHP code: the methods start with two underscores and have names like __set(), __isset(), __call(), etcetera.

The PHP-CPP library also has support for these magic methods. The methods are already defined in the Php::Base class (which is the base class for all classes that are written using the PHP-CPP library), and can be overridden in your derived class.

The nice thing about magic methods implemented with PHP-CPP is that they do not become visible from PHP user space. In other words, when you define a function like __set() or __unset() in your C++ class, these functions can not be called explicitly from PHP scripts - but they do get called when a property is accessed.

Pseudo properties

With the methods __get(), __set(), __unset() and __isset() you can define pseudo properties. It allows you to, for example, create read-only properties, or properties that are checked for validity when they are set.

The magic methods work exactly the same as their counterparts in PHP scripts do, so you can easily port PHP code that uses these properties to C++.


#include <phpcpp.h>

/**
 *  A sample class, that has some pseudo properties that map to native types
 */
class User : public Php::Base
{
private:
    /**
     *  Name of the user
     *  @var    std::string
     */
    std::string _name;
    
    /**
     *  Email address of the user
     *  @var    std::string
     */
    std::string _email;

public:
    /**
     *  C++ constructor and C++ destructpr
     */
    User() {}
    virtual ~User() {}

    /**
     *  Get access to a property
     *  @param  name        Name of the property
     *  @return Value       Property value
     */
    virtual Php::Value __get(const Php::Value &name) override
    {
        // check if the property name is supported
        if (name == "name") return _name;
        if (name == "email") return _email;
        
        // property not supported, fall back on default
        return Php::Base::__get(name);
    }
    
    /**
     *  Overwrite a property
     *  @param  name        Name of the property
     *  @param  value       New property value
     */
    virtual void __set(const Php::Value &name, const Php::Value &value) override
    {
        // check the property name
        if (name == "name") 
        {
            // store member
            _name = value.stringValue();
        }
        
        // we check emails for validity
        else if (name == "email")
        {
            // store the email in a string
            std::string email = value;
            
            // must have a '@' character in it
            if (email.find('@') == std::string::npos) 
            {
                // email address is invalid, throw exception
                throw Php::Exception("Invalid email address");
            }
            
            // store the member
            _email = email;
        }
        
        // other properties fall back to default
        else
        {
            // call default
            Php::Base::__set(name, value);
        }
    }
    
    /**
     *  Check if a property is set
     *  @param  name        Name of the property
     *  @return bool
     */
    virtual bool __isset(const Php::Value &name) override
    {
        // true for name and email address
        if (name == "name" || name == "email") return true;
        
        // fallback to default
        return Php::Base::__isset(name);
    }
    
    /**
     *  Remove a property
     *  @param  name        Name of the property to remove
     */
    virtual void __unset(const Php::Value &name) override
    {
        // name and email can not be unset
        if (name == "name" || name == "email") 
        {
            // warn the user with an exception that this is impossible
            throw Php::Exception("Name and email address can not be removed");
        }
        
        // fallback to default
        Php::Base::__unset(name);
    }
};

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

The above example shows how you can create a User class that seems to have a name and email property, but that does not allow you to assign an email address without a '@' character in it, and that does not allow you to remove the properties.


<?php
// initialize user and set its name and email address
$user = new User();
$user->name = "John Doe";
$user->email = "john.doe@example.com";

// show the email address
echo($user->email."\n");

// remove the email address (this will cause an exception)
unset($user->email);
?>

Magic methods __call() and __invoke()

C++ methods need to be registered explicitly in your extension get_module() startup function to be accessible from PHP user space. However, when you override the __call() method, you can accept all calls - even calls to methods that do not exist. When someone makes a call from user space to something that looks like a method, it will be passed to this __call() method. In a script you can thus use $object->something(), $object->whatever() or $object->anything() - it does not matter what the name of the method is, all these calls are passed on to the __call() method in the C++ class.

Next to the magic __call() function, the PHP-CPP library also supports the __invoke() method. This is a method that gets called when an object instance is used as if it was a function. This can be compared with overloading the operator () in a C++ class. By implementing the __invoke() method, scripts from PHP user space can create an object, and then use it as a function.


#include <phpcpp.h>

/**
 *  A sample class, that accepts all thinkable method calls
 */
class MyClass : public Php::Base
{
public:
    /**
     *  C++ constructor and C++ destructpr
     */
    MyClass() {}
    virtual ~MyClass() {}

    /**
     *  Regular method
     *  @param  params      Parameters that were passed to the method
     *  @return Value       The return value
     */
    Php::Value regular(Php::Parameters &params)
    {
        return "this is a regular method";
    }

    /**
     *  Overriden __call() method to accept all method calls
     *  @param  name        Name of the method that is called
     *  @param  params      Parameters that were passed to the method
     *  @return Value       The return value
     */
    virtual Php::Value __call(const char *name, Php::Parameters &params) override
    {
        // the return value
        std::string retval = name;
        
        // loop through the parameters
        for (auto &param : params)
        {
            // append parameter string value to return value
            retval += " " + param.stringValue();
        }
        
        // done
        return retval;
    }

    /**
     *  Overridden __invoke() method so that objects can be called directly
     *  @param  params      Parameters that were passed to the method
     *  @return Value       The return value
     */
    virtual Php::Value __invoke(Php::Parameters &params) override
    {
        // the return value
        std::string retval = "invoke";

        // loop through the parameters
        for (auto &param : params)
        {
            // append parameter string value to return value
            retval += " " + param.stringValue();
        }
        
        // done
        return retval;
    }

};

/**
 *  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");
        
        // register the regular method
        myClass.method("regular", &MyClass::regular);
        
        // add the class to the extension
        myExtension.add(std::move(myClass));
        
        // return the extension
        return myExtension;
    }
}


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

// call a regular method
echo($object->regular()."\n");

// call some pseudo-methods
echo($object->something()."\n");
echo($object->myMethod(1,2,3,4)."\n");
echo($object->whatever("a","b")."\n");

// call the object as if it was a function
echo($object("parameter","passed","to","invoke")."\n");
?>

The above PHP script calls some method on this class. And will generate the following output:

regular
something
myMethod 1 2 3 4
whatever a b
invoke parameter passed to invoke

Casting methods (__toString and more)

In PHP you can add a __toString() method to a class. This method is automatically called when an object is casted to a string, or when an object is used in a string context. PHP-CPP supports this __toString() method too. But there are more casting methods offered by PHP-CPP.

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() {}
    virtual ~MyClass() {}

    /**
     *  Cast to a string
     *  @return Value
     */
    virtual Php::Value __toString() override
    {
        return "abcd";
    }
    
    /**
     *  Cast to a integer
     *  @return long
     */
    virtual long __toInteger() override
    {
        return 1234;
    }
    
    /**
     *  Cast to a floating point number
     *  @return double
     */
    virtual double __toFloat() override
    {
        return 88.88;
    }
    
    /**
     *  Cast to a boolean
     *  @return bool
     */
    virtual bool __toBool() override
    {
        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;
    }
}