Classes and objects

Serious business now. C++ and PHP are both object oriented programming languages, in which you can create classes and objects. The PHP-CPP library gives you the tools to combine these two and make native C++ classes accessible from PHP.

Sadly (but also logically if you think about it) not every thinkable C++ class can be directly exported to PHP. It takes a little more work (although not so much). For a start, you must make sure that your class is derived from Php::Base, and secondly, when you add your class to the extension object, you must also specify all methods that you want to make accessible from PHP.


#include <phpcpp.h>

// actual class implementation
class Counter : public Php::Base
{
private:
    int _value = 0;

public:
    Counter() {}
    virtual ~Counter() {}
    
    Php::Value increment() { return ++_value; }
    Php::Value decrement() { return --_value; }
    Php::Value value() const { return _value; }
};

extern "C" {
    PHPCPP_EXPORT void *get_module() {
        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");
        counter.method("increment", &Counter::increment);
        counter.method("decrement", &Counter::decrement);
        counter.method("value", &Counter::value);
        
        // add the class to the extension
        myExtension.add(std::move(counter));
        
        // return the extension
        return myExtension;
    }
}

Let's talk about programming conventions first - we always use capitals for the first letter of a classname, and member variables always start with an underscore. Every class always has a destructor, and it always is virtual. That's just a convention - our convention - and you do not have to follow that.

On topic. The example shows a very simple Counter class with three methods: increment(), decrement() and value(). The two update methods return the value of the counter after the operation, the value() method returns the current value.

If you want to make a class method that is accessible from PHP, you must ensure that it has one of the eight supported signatures (which are the same signatures that exportable plain functions can have), plus their const variant.


void YourClass::example1();
void YourClass::example2(Php::Parameters &params);
Php::Value YourClass::example3();
Php::Value YourClass::example4(Php::Parameters &params);
void YourClass::example5() const;
void YourClass::example6(Php::Parameters &params) const;
Php::Value YourClass::example7() const;
Php::Value YourClass::example8(Php::Parameters &params) const;

Methods work exactly the same as regular functions, with the difference that in a method you have (of course) access to the member variables of the object (and in C++ you you do not have to use "this->" to access member variables).

To make the class accessible from PHP, you must add it to the extension object inside the get_module() function. The Php::Class template class should be be used for that. The template parameter should be your implementation class, so that the Php::Class object internally knows which class to instantiate the moment the "new" operator is used inside a PHP script.

The Php::Class constructor needs a string parameter, with the name of the class in PHP. The method Php::Class::method() can be used to register the methods that you want to make accessible from PHP. Notice that in our example we have used the C++11 std::move() function to add the class to the extension, so that the class object is actually moved into the extension object, which is a more efficient operation than copying.

Method parameters

Methods are just like functions, and just how you use the Php::ByVal and the Php::ByRef classes to specify the parameters of a function, you can specify method parameters too.


#include <phpcpp.h>

// actual class implementation
class Counter : public Php::Base
{
private:
    int _value = 0;

public:
    Counter() {}
    virtual ~Counter() {}
    
    Php::Value increment(Php::Parameters ¶ms) 
    { 
        return _value += params.size() > 0 ? (int)params[0] : 1; 
    }
    
    Php::Value decrement(Php::Parameters ¶ms) 
    { 
        return _value -= params.size() > 0 ? (int)params[0] : 1; 
    }
    
    Php::Value value() const 
    { 
        return _value; 
    }
};

extern "C" {
    PHPCPP_EXPORT void *get_module() {
        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");
        
        // register the increment method, and specify its parameters
        counter.method("increment", &Counter::increment, { 
            Php::ByVal("change", Php::Type::Numeric, false) 
        });
        
        // register the decrement, and specify its parameters
        counter.method("decrement", &Counter::decrement, { 
            Php::ByVal("change", Php::Type::Numeric, false) 
        });
        
        // register the value method
        counter.method("value", &Counter::value);
        
        // add the class to the extension
        myExtension.add(std::move(counter));
        
        // return the extension
        return myExtension;
    }
}

In the code above we have modified our first example. The increment and decrement method now get an optional 'change' parameter, which is a numeric value that holds the change that should be applied to the counter. Note that this parameter is optional - so inside the method implementation we check if the number of parameters is bigger than zero to prevent nasty segmentation faults.


<?php
$counter = new Counter();
$counter->increment(5);
$counter->increment();
$counter->decrement(3);
echo($counter->value()."\n");
?>

The code above shows a PHP script that uses the native Counter class. The output of above script is (as you had of course expected) 3.

In the example code we have not shown how to use the Php::ByRef class, but that works exaclty the same as for functions, so we thought that an example was not really necessary (and we're not a big fan of parameters-by-reference either).

Access modifiers

In PHP (and in C++ too) you can mark methods as public, private or protected. To achieve this for your native class too, you should pass in an additional flags parameter when you add the method to the Php::Class object. Imagine that you want to make the increment and decrement methods in the previous example protected, then you can simply add a flag:


extern "C" {
    PHPCPP_EXPORT void *get_module() {
        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");
        
        // register the increment method, and specify its parameters
        counter.method("increment", &Counter::increment, Php::Protected, { 
            Php::ByVal("change", Php::Type::Numeric, false) 
        });
        
        // register the decrement, and specify its parameters
        counter.method("decrement", &Counter::decrement, Php::Protected, { 
            Php::ByVal("change", Php::Type::Numeric, false) 
        });
        
        // register the value method
        counter.method("value", &Counter::value, Php::Public | Php::Final);
        
        // add the class to the extension
        myExtension.add(std::move(counter));
        
        // return the extension
        return myExtension;
    }
}

By default, every method (and every property too, but we deal with that later) is public. You can pass in an additional Php::Protected or Php::Private flag if you want to mark a method as either protected or private. The flag parameter can be bitwise-or'ed with Php::Abstract or Php::Final if you also want to mark your method as either abstract or final. We did this with the value() method, so that it becomes impossible to override this method in a derived class.

Remember that the exported methods in your C++ class must always be public - even when you've marked them as private or protected in PHP. This makes sense, because after all, your methods are called by the PHP-CPP library, and if you make them private, they becomes invisible for the library.

Abstract and final

In the previous section we showed how to use the Php::Final and Php::Abstract flags to create a final or abstract method. If you want to make your entire class abstract or final, you can do so by using Php::FinalClass or Php::AbstractClass instead of Php::Class.


extern "C" {
    PHPCPP_EXPORT void *get_module() {
        static Php::Extension myExtension("my_extension", "1.0");
        
        // description of the class so that PHP knows which methods are accessible
        Php::FinalClass<Counter> counter("Counter");
        
        // register methods
        ...
        
        // return the extension
        return myExtension;
    }
}

It may seem strange that you have to pass in the address of a real C++ method when you register an abstract method. Abstract methods do normally not have an implementation, so what do you need this C++ implementation for? Luckily, there also is a different way for registering abstract methods.


extern "C" {
    PHPCPP_EXPORT void *get_module() {
        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");
        
        // register an abstract method
        counter.method("myAbstractMethod", { Php::ByVal("value", Php::Type::String, true) });
        
        // register other methods
        ...
        
        // return the extension
        return myExtension;
    }
}

To register abstract methods, you can simply use an alternative form of the Counter::method() method that does not take a pointer to a C++ method.

There is much more to say about classes and objects, in the next section we'll explain constructors and destructors.