Русская документация PHP-CPP

Конструкторы

Есть небольшая, но существенная разница между конструкторами и диструкторами C++ и PHP.

С++ конструктор вызывается до инициализации объекта. Т.е. в тот момент, когда объект еще не создан и не размещен в памяти. И сама инициализация происходит при непосредственном участии конструктора.

К примеру вы можете попытаться вызвать виртуальный метод из конструктора в С++. Даже если этот виртуальный метод был переопределен в производном классе, его вызов приведет к вызову собственной реализации, но не переопределенной. Причина в том, что в момент вызова конструктора, объект еще не инстацирован. Память для него не выделена и объект не имеет представление о своем положении в иерархии классов.

В PHP, напротив, в момент вызова конструктора, объект уже инициализирован. И конструктор, таким образом, служет не для полноценного инстацирования объекта, а для уточнения его начального состояния. Это позволяет осуществлять вызов даже абстрактных методов, переопределеннх в производном классе, непосредственно из конструктора.

Следующий PHP-скрипт является полностью допустимым. Но ничего подобного не может быть сделано в C++.

<?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();
?>

Данный скрипт выводит 'doSomething()'. Причина этого заключается в том, что __construct() на самом деле — это обычный метод, который автоматически вызывается сразу после создания объекта оператором new и является первым методом вызываемым у объекта созданным обычным способом (в отличии, от объекта созданного клонированием)

C++ программисту нужно всегда учитывать эту разницу. Не стоит путать c++-конструктор и php-конструктор, который является просто специальным методом.

Теперь, когда объяснены смысл и различия php и C++ конструкторов, можно описать как происходит создание объектов внутри PHP-CPP. Давайте рассмотрим что происходит, когда мы создаем PHP объект, описанный в расширении созданном с помощью библиотеки PHP-CPP.

После того как объект описанный в C++ коде будет создан, указатель на него передается ядру php. И ядро php создает php объект на основе уже созданного c++ объекта. И уже после того как создан php-объект вызываются методы, уточняющие первоночальное состояние объекта. Такие как __construct() и __clone().

PHP объект может быть создан вообще без вызова конструирующих методов __construct() и __clone(). Например, он может быть возвращен в пространство пользователя из внешней функции или статического метода.

Рассмотрим пример.

#include <phpcpp.h>

/**
 *  Simple counter class
 */
class Counter : public Php::Base
{
private:
    /**
     *  Internal value
     *  @var int
     */
    int _value = 0;

public:
    /**
     *  c++ constructor
     */
    Counter() {}
    
    /**
     *  c++ destructor
     */
    virtual ~Counter() {}
    
    /**
     *  php "constructor"
     *  @param  params
     */
    void __construct(Php::Parameters &params)
    {
        // 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; }
};

/**
 *  Switch to C context so that the get_module() function can be
 *  called by C programs (which the Zend engine is)
 */
extern "C" {
    /**
     *  Startup function for the extension
     *  @return void*
     */
    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;
    }
}

Рассмотренный выше код демонстрирует, что метод __construct() был зарегистрирован как обычный регулярный метод, что на самом деле так и есть.

<?php
$counter = new Counter(10);
$counter->increment();
echo($counter->value()."\n");
?>
Поскольку __construct() рассматривается как регулярный метод, возможно также указать его параметры, область видимости (public, private или protected). Метод __construct() можно напрямую вызвать из PHP в пространстве пользователя, а в методах производного классавызывать parent::__construct().
<?php
$counter = new Counter(10);
$counter->increment();
$counter->__construct(5);
?>

В свете всего выше сказаннного, стоит отметить, что если вы объявите php конструктор закрытым (private) или защищенным (protected), то при попытке создать объект такого php класса вызововм оператора new объект физически будет создан. Затем ядро PHP попытается вызвать метод __construct(), предварительно проверив для него флаг области видимости. Обнаружив, что метод __construct() не может быть выван в пространстве пользователя PHP выдаст сообщение об ошибке и отменит вызов метода __construct(). Поскольку в этом случае только что созданный объект оказывается не связанным ни с какой переменной PHP, сборщик мусора PHP тут же инициирует удаление объекта, вызвав сначала PHP деструктор. Затем произойдет освобождение памяти занимаемой объектом, что приведет к вызову C++ деструктора.

Клонирование объектов

Если ваш C++ класс имеет конструктор копирования, то он автоматически становится клонируемым в PHP смысле (clonable). Т.е. для него автоматически регестрируется PHP метод __clone(). Если вам необходимо явно запретить клонирование ваших PHP объектов, вы можете поступить двумя способами: 1) явно удалить копирующий конструктор из вашего C++ класса (запретить компилятору создавать копирующий конструктор по умолчанию), или 2) объявить метод __clone() закрытым или защещенным.

#include <phpcpp.h>

/**
 *  Simple counter class
 */
class Counter : public Php::Base
{
private:
    /**
     *  Internal value
     *  @var int
     */
    int _value = 0;

public:
    /**
     *  c++ constructor
     */
    Counter() {}
    
    /**
     *  Удаляем конструктор копирования
     *  
     *  При удалении конструктор копирования, клонирование PHP объекта,
     *  порождаемого этим классом, автоматически становится невозможным.
     *  PHP выдаст предуприждение об ошибке, если попытаться клонировать объект в пользовательском коде.
     *  
     *  
     *  @param  counter
     */
    Counter(const Counter &counter) = delete;
    
    /**
     *  c++ destructor
     */
    virtual ~Counter() {}
    
    /**
     *  php "constructor"
     *  @param  params
     */
    void __construct(Php::Parameters &params)
    {
        // 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; }
};

/**
 *  Switch to C context so that the get_module() function can be
 *  called by C programs (which the Zend engine is)
 */
extern "C" {
    /**
     *  Startup function for the extension
     *  @return void*
     */
    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);
        
        // alternative way to make an object unclonable
        counter.method("__clone", Php::Private);
        
        // add the class to the extension
        myExtension.add(std::move(counter));
        
        // return the extension
        return myExtension;
    }
}

В приведенном вышше коде мы продимонстрировали оба способа сделать объект неклонируемым. Но использование только одного из способов вполне достаточно.

Альтернативные способы создания объектов

Класс Php::Value — суть обертка для PHP переменных. И раз уж возможно хранить объекты в переменных в пользовательском PHP коде, то тоже справедливо и для кода расширений написанных на PHP-CPP. Для целей хранения PHP объектов в коде расширений предназначен специальный класс Php::Object, который является производным классом от Php::Value с переопределенными конструкторами и некоторыми дополнительными проверками, предотвращающими ситуацию, когда в экземпляр класса Php::Object будет помещено нечто отличающееся от PHP объекта.

// Новая переменная, содержащая строку "Counter"
Php::Value strCounter("Counter");

// Новая переменная содержащая объект экземпляр класса Counter,
// PHP Конструктор __construct() будет вызван без параметров
Php::Object objCounter1("Counter");

// Новая переменная содержащая объект экземпляр класса Counter,
// PHP Конструктор будет вызван с одним параметром: __construct(10)
Php::Object objCounter2("Counter", 10);

// Новая переменная содержащая объект экземпляр  встроенного класса DateTime с
// PHP Конструктор будет вызван с одним параметром: __construct('now')
Php::Object time("DateTime", "now");


// Допустимое присваивание, a Php::Object расширяет класс Php::Value,
// и может быть присвоен экземпляру класса Php::Value
Php::Value copy1 = objCounter1;

// Не допустимое присваивание, Php::Object не может содержать переменные не являющиеся PHP объектами
Php::Object copy2 = strCounter;

Конструктор Php::Object получает имя класса, и дополнительный список аргументов, которые будут переданы в метод __construct() созданного объекта. В качестве имени класса можно использовать имена встроенных в PHP классов, классов из других расширений (например, DateTime), классов из ваших расширений в т.ч. написаных с помощью PHP-CPP, и даже классы из пользовательского кода PHP.

Вы можете вообще обойтись без вызовов методов __construct() или __clone() и создать PHP объект в своем C++ коде, поместить его в переменную и вернуть в пользовательский PHP код уже готовый объект.

Php::Value yourFunction()
{
    return Php::Object("MyClass", new MyClass());
}
Для этого предназначена вторая форма конструктора Php::Object:
    /**
     *  @param  name        Name of the class to instantiate
     *  @param  base        Implementation of the class
     */
    Object(const char *name, Base *base);
Следующий пример демонстрирует использование второй формы конструктора Php::Object:
#include <phpcpp.h>

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

public:
    // c++ constructor
    Counter(int value) : _value(value) {}
    
    // c++ destructor
    virtual ~Counter() {}
    
    // php "constructor"
    void __construct() {}

    // functions to increment and decrement
    Php::Value value() const { return _value; }
};

// function to create a new Counter
Php::Value createCounter()
{
    return Php::Object("Counter", new Counter(100));
}

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,
        // the __construct method is private because PHP scripts are not allowed
        // to create Counter instances
        Php::Class<Counter> counter("Counter");
        counter.method("__construct", &Counter::__construct, Php::Private);
        counter.method("value", &Counter::value);
        
        // add the class to the extension
        myExtension.add(std::move(counter));
        
        // add the factory function to create a Counter to the extension
        myExtension.add("createCounter", createCounter);
        
        // return the extension
        return myExtension;
    }
}
В приведеном выше коде мы сделали метод __construct() закрытым. Это делает невозможным создание экземпляров этого класса из пользовательского PHP кода и через вызов Php::Object("Counter")

Обратите внимание, что при создании PHP объекта с помощью Php::Object вы должны передавать имя класса в конструктор. Это необходимо, поскольку в C++ классы не содержат какой-либо информации о себе (в том числе собственное имя), но PHP подобная информация необходима, например для таких функций get_class().

Как и в случае с клонированием, вы можете удалить C++ конструктор по умолчанию. В этом случае объект не сможет быть создан из пользовательского кода а PHP конструктор __construct() автоматически станет закрытым.

Например приведенный выше код можно без ущерба заменить следующим

#include <phpcpp.h>

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

public:
    // c++ constructor
    Counter(int value) : _value(value) {}
    
    // c++ destructor
    virtual ~Counter() {}
    
    // functions to increment and decrement
    Php::Value value() const { return _value; }
};

// function to create a new Counter
Php::Value createCounter()
{
    return Php::Object("Counter", new Counter(100));
}

extern "C" {
    PHPCPP_EXPORT void *get_module() {
        static Php::Extension myExtension("my_extension", "1.0");
        
        // Описание класса. Сообщаем PHP какие методы ему доступны
        // И добавляем наш класс в наше расширение
        myExtension.add(std::move(Php::Class<Counter>("Counter")
            .method("value", &Counter::value);
        ));
        
        // add the factory function to create a Counter to the extension
        myExtension.add("createCounter", createCounter);
        
        // return the extension
        return myExtension;
    }
}
Установка PHP-CPP Загрузка расширений Ваше первое расширение Вывода и ошибок Функции Параметры Вызов функций и методов Классы и объекты Конструкторы и деструкторы Наследование Магические методы Магические интерфейсы Генерация исключений Специальные возможности Поля классов Работа с переменными ini записи Extension callbacks Пространства имен