Constructors and destructors

There is a small but very important difference between constructor and destructors in C++, and the __construct() and __destruct() methods in PHP.

A C++ constructor is called on an object that is being initialized, but that is not in an initialized state yet. You can experience this by calling a pure virtual method from a constructor. This will make your program crash, even when the pure virtual method was implemented in the derived class. The reason for this is that inside the C++ constructor the object is not yet fully initialized, and the object is not yet aware of it's position in the class hierarchy. The call to the pure virtual method can thus not be passed on to the derived object.


#include <iostream>

// define a base class with a pure virtual method that is called from the
// constructor
class BASE 
{
public:
    // constructor
    BASE() 
    {
        // call the pure virtual method
        doSomething();
    }
    
    // define method that should be implemented by derived classes
    virtual void doSomething() = 0;
};

// define a derived class
class DERIVED : public BASE 
{
public:
    // implementation of the virtual function
    virtual void doSomething() override 
    {
        std::cout << "doSomething()" << std::endl;
    }
};

// main procedure
int main()
{
    DERIVED d;
    return 0;
}

The above program crashes (some compilers even refuse to compile this). Unlike similar code in PHP. In PHP however, when the __construct() method gets called, the object is already fully initialized and it is perfectly legal to make calls to abstract methods that are implemented in derived classes, as you can see in the following example.


<?php

// base class in PHP, in which the an abstract method is called
abstract class BASE 
{
    // constructor
    public function __construct() 
    {
        // call abstract method
        $this->doSomething();
    }
    
    // abstract method to be implemented by derived classes
    public abstract function doSomething();
}

// the derived class
class DERIVED extends BASE 
{
    // implement the abstract method
    public function doSomething() 
    {
        echo("doSomething()\n");
    }
}

// create an instance of the derived class
$d = new DERIVED();
?>    

This PHP script correctly outputs 'doSomething()'. This happens because the __construct() method in PHP is not a real constructor. It constructs nothing, it has access to all members (that already are constructed), and (when available) also the base class and overridden methods. In fact, __construct() is a normal method that just happens to be the very first method that is called right after the object was constructed, and that is called automatically.

This difference is important for you as a C++ programmer, because you should never confuse your C++ constructor with the __construct() method. In the real constructor, the C++ object is being constructed while the PHP object does not yet exist. After the constructor is finished, the PHP object is created, and the __construct() method gets called. It is therefore valid to have both a C++ constructor and a __construct() method in your class.


#include <phpcpp.h>

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

public:
    // c++ constructor
    Counter() {}
    
    // c++ destructor
    virtual ~Counter() {}
    
    // php "constructor"
    void __construct(Php::Parameters ¶ms)
    {
        // copy first parameter (if available)
        if (params.size() > 0) _value = params[0];
    }

    // functions to increment and decrement
    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("__construct", &Counter::__construct);
        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;
    }
}

The code above shows that __construct() is registered as if it was a regular method - and that's what it is. The counter example that we've used before is now extended so that it is possible to give it an initial value.


<?php
$counter = new Counter(10);
$counter->increment();
echo($counter."\n");
?>