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 method __call()

C++ methods need to be registered explicitly in your extension get_module() startup function to make them accessible. However, when you override the __call() method, you can accept all possible method calls. It does not matter what the name of the method is, when something that looks like a method is called from PHP user space, it will trigger a call to your __call() method.


#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() {}

    /**
     *  Override the __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
     */
    Php::Value __call(const char *name, Php::Parameters &params)
    {
        // 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;
    }
};

/**
 *  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;
    }
}


<?php
// initialize an object
$object = new MyClass();
echo($object->something()."\n");
echo($object->myMethod(1,2,3,4)."\n");
echo($object->whatever("a","b")."\n");
?>

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

something
myMethod 1 2 3 4
whatever a b