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

К сожалению (но и логично, если вы подумаете об этом) не каждый мыслимый класс C++ может быть непосредственно экспортирован в PHP. Требуется немного больше работы (хотя и не так много). Для начала, вы должны убедиться, что ваш класс является производным от Php:: Base, и во-вторых, когда вы добавляете ваш класс к объекту расширения, вы также должны указать все методы, которые вы хотите сделать доступными из PHP.

#include <phpcpp.h>

/**
 *  Класс счетчика, который можно использовать для подсчета
 */
class Counter : public Php::Base
{
private:
    /**
     *  Начальное значение
     *  @var    int
     */
    int _value = 0;

public:
    /**
     *  Конструктор и деструктор C++ 
     */
    Counter() = default;
    virtual ~Counter() = default;

    /**
     *  Обновить методы для увеличения или уменьшения счетчика.
     *  оба метода возвращают новое значение счетчика
     *  @return int
     */
    Php::Value increment() { return ++_value; }
    Php::Value decrement() { return --_value; }

    /**
     *  Метод получения текущего значения счетчика
     *  @return int
     */
    Php::Value value() const { return _value; }
};

/**
 *  Переключиться в контекст C, чтобы убедиться, что функция get_module() 
 *  может быть вызвана программами на языке C)
 */
extern "C" {
    /**
     *  Функция запуска, вызываемая движком Zend 
     *  для получения всей информации о расширении
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() {
        // создать статический экземпляр объекта расширения
        static Php::Extension myExtension("my_extension", "1.0");

        // описание класса, чтобы PHP знал, какие методы доступны
        Php::Class<Counter> counter("Counter");
        counter.method<&Counter::increment> ("increment");
        counter.method<&Counter::decrement> ("decrement");
        counter.method<&Counter::value>     ("value");

        // добавить класс к расширению
        myExtension.add(std::move(counter));

        // вернуть расширение
        return myExtension;
    }
}

Давайте сначала поговорим о соглашениях программирования - мы всегда используем заглавные буквы для первой буквы имени класса, а переменные-члены всегда начинаются с подчеркивания. У каждого класса всегда есть деструктор, и он всегда является виртуальным. Это просто конвенция - наша конвенция - и вам не обязательно следовать ей.

По теме. В примере показан очень простой класс Counter с тремя методами: increment (), decrement () и value (). Два метода обновления возвращают значение счетчика после операции, метод value() возвращает текущее значение.

Если вы хотите, чтобы метод класса был доступен из PHP, вы должны убедиться, что он соответствует одной из поддерживаемых подписей. Это, по сути, те же самые подписи, что и экспортируемые простые функции, но с версиями для методов const и non-const.

// подписи поддерживаемых регулярных методов
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;

Методы работают точно так же, как и обычные функции, с той разницей, что в методе у вас (конечно) доступ к переменным-членам объекта.

Чтобы сделать класс доступным из PHP, необходимо добавить его в объект расширения внутри функции get_module(). Для этого следует использовать шаблон класса Php::Class. Параметр template должен быть вашим классом реализации, чтобы объект Php::Class внутренне знал, какой экземпляр класса создать в момент использования оператора " new " внутри PHP-скрипта.

Конструктору Php::Class требуется строковый параметр с именем класса в PHP. Затем можно использовать метод Php::Class::method(), как вы можете видеть в примере выше, для регистрации методов, которые вы хотите сделать доступными из PHP. Вы видели, что в примере мы использовали функцию C++11 std::move() для добавления класса в расширение? Это фактически переместит объект класса в расширение, что является более эффективной операцией, чем копирование.

Параметры метода

Методы похожи на функции, и так же, как вы используете классы Php::ByVal и the Php::ByRef чтобы указать параметры функции, вы также можете указать параметры метода.

#include <phpcpp.h>

/**
 *  Класс счетчика, который можно использовать для подсчета
 */
class Counter : public Php::Base
{
private:
    /**
     *  The internal value
     *  @var    int
     */
    int _value = 0;

public:
    /**
     *  Конструктор и деструктор C++
     */
    Counter() {}
    virtual ~Counter() {}

    /**
     *  Операция инкремента
     *  этот метод получает один необязательный параметр, содержащий изменение
     *  @param  int     Optional increment value
     *  @return int     New value
     */
    Php::Value increment(Php::Parameters &params) 
    { 
        return _value += params.empty() ? 1 : (int)params[0];
    }

    /**
     *  Операция декремента
     *  этот метод получает один необязательный параметр, удерживающий изменение
     *  @param  int     Optional decrement value
     *  @return int     New value
     */
    Php::Value decrement(Php::Parameters &params) 
    { 
        return _value -= params.empty() ? 1 : (int)params[0]; 
    }

    /**
     *  Метод получения текущего значения
     *  @return int
     */
    Php::Value value() const 
    { 
        return _value; 
    }
};

/**
 *  Переключиться в контекст C, чтобы убедиться, что функция get_module()
 *  может быть вызвана программами на языке C)
 */
extern "C" {
    /**
     *  Функция запуска, вызываемая движком Zend 
     *  для получения всей информации о расширении
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() {
        // создать статический экземпляр объекта расширения
        static Php::Extension myExtension("my_extension", "1.0");

        // описание класса, чтобы PHP знал, какие методы доступны
        Php::Class<Counter> counter("Counter");

        // зарегистрировать метод инкремента и указать его параметры
        counter.method<&Counter::increment>("increment", { 
            Php::ByVal("change", Php::Type::Numeric, false) 
        });

        // зарегистрировать декремент и указать его параметры
        counter.method<&Counter::decrement>("decrement", { 
            Php::ByVal("change", Php::Type::Numeric, false) 
        });

        // зарегистрировать метод value
        counter.method<&Counter::value>("value", {});

        // добавить класс к расширению
        myExtension.add(std::move(counter));

        // вернуть расширение
        return myExtension;
    }
}

В приведенном выше коде мы изменили наш первый пример. Теперь метод increment and decment получает необязательный параметр «change», который представляет собой числовое значение, которое содержит изменение, которое должно быть применено к счетчику. Обратите внимание, что этот параметр является необязательным - поэтому внутри реализации метода мы проверяем, больше ли число параметров больше нуля, чтобы предотвратить неприятные ошибки сегментации.

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

В приведенном выше коде показан скрипт PHP, который использует собственный класс Counter. Вывод вышеуказанного скрипта (как вы, конечно, ожидали) 3.

В примере кода мы не показали, как использовать класс Php :: ByRef, но это работает так же, как и для функций, поэтому мы подумали, что пример не нужен (и мы не являемся большим поклонником параметров) по ссылке).

Статические методы

Статические методы также поддерживаются. Статический метод-это метод, который не имеет доступа к указателю "this". Поэтому в C++ такие статические методы идентичны обычным функциям,которые также не имеют доступа к указателю' this'. Единственное различие между статическими методами C++ и обычными функциями C++ заключается во время компиляции: компилятор разрешает статическим методам доступ к закрытым данным. Однако сигнатура статического метода полностью идентична сигнатуре обычной функции.

PHP-CPP позволяет регистрировать статические методы. Но из-за того, что сигнатура статического метода идентична сигнатуре обычной функции, метод, который вы регистрируете, даже не обязательно должен быть методом того же класса. Обычные функции и статические методы других классов имеют точно такую же сигнатуру и тоже могут быть зарегистрированы! С точки зрения архитектуры программного обеспечения лучше использовать только статические методы того же класса, но C++ позволяет сделать гораздо больше.

#include <phpcpp.h>

/**
 *  Regular function
 *
 *  Поскольку обычная функция не имеет указателя' this',
 *  она имеет ту же сигнатуру, что и статические методы
 *
 *  @param  params      Параметры, передаваемые функции
 */
void regularFunction(Php::Parameters &params)
{
    // @todo добавить реализацию
}

/**
 *  Очень простой класс, который не будет экспортироваться в PHP
 */
class PrivateClass
{
public:
    /**
     *  Конструктор и деструктор C++
     */
    PrivateClass() = default;
    virtual ~PrivateClass() = default;

    /** 
     *  Статический метод
     *
     *  Статический метод также не имеет указателя 'this'
     *  и поэтому имеет сигнатуру, идентичную обычным функциям
     *
     *  @param  params      Параметры, передаваемые в метод
     */
    static void staticMethod(Php::Parameters &params)
    {
        // @todo добавить реализацию
    }
};

/**
 *  Очень простой класс, который будет экспортирован в PHP
 */
class PublicClass : public Php::Base
{
public:
    /**
     *  Конструктор и деструктор C++
     */
    PublicClass() = default;
    virtual ~PublicClass() = default;

    /** 
     *  Другой статический метод
     *
     *  Этот static имеет точно такую же сигнатуру, как обычная 
     *  функция и статический метод, которые были упомянуты
     *  ранее
     *
     *  @param  params      Параметры, передаваемые в метод
     */
    static void staticMethod(Php::Parameters &params)
    {
        // @todo добавить реализацию
    }
};

/**
 *  Переключитесь в контекст C, чтобы убедиться, что функция get_module()
 *  может быть вызвана программами на языке C)
 */
extern "C" {
    /**
     *  Функция запуска, вызываемая движком Zend
     *  для получения всей информации о расширении
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() {
        // создать статический экземпляр объекта расширения
        static Php::Extension myExtension("my_extension", "1.0");

        // описание класса, чтобы PHP знал, какие методы доступны
        Php::Class<PublicClass> myClass("MyClass");

        // зарегистрировать PublicClass :: staticMethod как
        // статический метод, вызываемый из PHP
        myClass.method<&PublicClass::staticMethod>("static1");

        // регулярные функции имеют те же подписи,  
        // что и статические методы. Поэтому ничто не запрещает вам
        // регистрировать нормальную функцию как статический метод
        myClass.method<regularFunction>("static2");

        // и даже статические методы из совершенно разных классов
        // имеют одну и ту же сигнатуру функций
        // и поэтому могут быть зарегистрированы
        myClass.method<&PrivateClass::staticMethod>("static3");

        // добавить класс к расширению
        myExtension.add(std::move(myClass));

        // На самом деле, поскольку статический метод имеет ту же подпись,
        // что и регулярная функция, вы также можете регистрировать
        // статические методы C ++ как регулярные глобальные функции PHP
        myExtension.add("myFunction", &PrivateClass::staticMethod);

        // вернуть расширение
        return myExtension;
    }
}

Сомнительно, насколько полезно это все. Вероятно, рекомендуется сохранить ваш код в чистоте, простоте и обслуживании, и только регистрировать статические методы PHP, которые также находятся в статических методах C ++ того же класса. Но C ++ не запрещает вам делать это совершенно иначе. Давайте рассмотрим пример, как вызвать статические методы

<?php
// это вызовет PublicClass::staticMethod()
MyClass::static1();

// это вызовет PrivateClass::staticMethod()
MyClass::static2();

// это вызовет regularFunction()
MyClass::static3();

// это также вызовет PrivateClass::staticMethod()
myFunction();
?>

Модификаторы доступа

В PHP (и в C++ тоже) можно пометить методы как public, private или protected. Чтобы достичь этого и для вашего собственного класса, вы должны передать дополнительный параметр flags при добавлении метода в объект Php::Class. Представьте, что вы хотите сделать методы инкремента и декремента в предыдущем примере защищенными, тогда вы можете просто добавить флаг:

/**
 *  Переключиться в контекст C, чтобы убедиться, что функция get_module()
 *  может быть вызвана программами C (для которых движок Zend)
 */
extern "C" {
    /**
     *  Функция запуска, вызываемая движком Zend 
     *  для получения всей информации о расширении
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() {
        // создать статический экземпляр объекта расширения
        static Php::Extension myExtension("my_extension", "1.0");

        // описание класса, чтобы PHP знал, какие методы доступны
        Php::Class<Counter> counter("Counter");

        // зарегистрировать метод инкремента и указать его параметры
        counter.method<&Counter::increment>("increment", Php::Protected, { 
            Php::ByVal("change", Php::Type::Numeric, false) 
        });

        // зарегистрировать декремент и указать его параметры
        counter.method<&Counter::decrement>("decrement", Php::Protected, { 
            Php::ByVal("change", Php::Type::Numeric, false) 
        });

        // зарегистрировать метод value
        counter.method<&Counter::value>("value", Php::Public | Php::Final);

        // добавить класс к расширению
        myExtension.add(std::move(counter));

        // вернуть расширение
        return myExtension;
    }
}

По умолчанию каждый метод (и каждое свойство тоже, но мы разберемся с этим позже) является открытым. Вы можете передать дополнительный флаг Php::Protected или Php::Private, если хотите пометить метод как защищенный или закрытый. Параметр flag может быть побитовым или с Php:: Abstract или Php::Final, если вы также хотите пометить свой метод как абстрактный или final. Мы сделали это с помощью метода value (), так что переопределить этот метод в производном классе становится невозможно.

Помните, что экспортируемые методы в вашем классе C++ всегда должны быть public - даже если вы отметили их как private или protected в PHP. Это имеет смысл, потому что в конце концов, ваши методы вызываются библиотекой PHP-CPP, и если вы сделаете их private, они становятся невидимыми для библиотеки.

Абстрактные и финальные

В предыдущем разделе мы показали, как использовать флаги Php::Final и Php::Abstract для создания окончательного или абстрактного метода. Если вы хотите сделать весь класс абстрактным или окончательным, вы можете сделать это, передав этот флаг конструктору Php::Class.

/**
 *  Переключитесь на C-контекст, чтобы гарантировать, что функция get_module ()
 *  может быть вызвана программами C (для которых движок Zend)
 */
extern "C" {
    /**
     *  Функция запуска, вызываемая движком Zend 
     *  для получения всей информации о расширении
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() {
        // создать статический экземпляр объекта расширения
        static Php::Extension myExtension("my_extension", "1.0");

        // описание класса, чтобы PHP знал, какие методы доступны
        Php::Class<Counter> counter("Counter", Php::Final);

        // регистрация методов 
        ...

        // вернуть расширение
        return myExtension;
    }
}

Как мы уже объясняли ранее, когда вы хотите зарегистрировать абстрактный метод, вы должны передать флаг Php::Abstract для вызова метода Php::Class::method(). Однако может показаться странным, что этот метод также требует, чтобы вы перешли в адрес реального C ++-метода. Абстрактные методы обычно не имеют реализации, так что вам нужно указать указатель на метод? К счастью, существует и другой способ регистрации абстрактных методов.

/**
 *  Переключитесь на C-контекст, чтобы гарантировать, что функция get_module ()
 *  может быть вызвана программами C (для которых движок Zend)
 */
extern "C" {
    /**
     *  Функция запуска, вызываемая движком Zend 
     *  для получения всей информации о расширении
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() {
        // создать статический экземпляр объекта расширения
        static Php::Extension myExtension("my_extension", "1.0");

        // описание класса, чтобы PHP знал, какие методы доступны
        Php::Class<Counter> counter("Counter");

        // зарегистрировать абстрактный метод
        // не нужно передавать указателем на этот метод или передавать флаг: 
        // метод thet автоматически становится абстрактным,
        // если не указывается адрес метода C ++
        counter.method("myAbstractMethod", { 
            Php::ByVal("value", Php::Type::String, true) 
        });

        // регистрировать другие методы
        ...

        // добавить счетчик к расширению
        // (или переместить его в расширение, что происходит быстрее)
        myExtension.add(std::move(counter));

        // вернуть расширение
        return myExtension;
    }
}

Чтобы зарегистрировать абстрактные методы, вы можете просто использовать альтернативную форму метода Counter::method (), которая не принимает указатель на метод C ++.

О классах и объектах можно сказать гораздо больше, в следующем разделе мы расскажем о конструкторах и деструкторах.