Каждый класс PHP имеет «магические методы». Возможно, вы уже знаете эти методы при написании PHP-кода: методы начинаются с двух символов подчеркивания и имеют имена, такие как __set (), __ isset (), __call () и т. Д.

Библиотека PHP-CPP также поддерживает эти магические методы. Используя некоторые приемы компилятора C++, компилятор C++ определяет, существуют ли методы в вашем классе, и если они существуют, они будут скомпилированы в ваше расширение и вызваны, когда они будут доступны из PHP.

Определение времени компиляции

Хотя вы, возможно, ожидали, что магические методы - это виртуальные методы в классе Php :: Base, которые могут быть переопределены, это не так. Методы обнаруживаются компилятором C ++ во время компиляции - и это весьма обычные методы, которые просто имеют определенное имя.

Из-за определения времени компиляции сигнатура методов является несколько гибкой.  Возвращаемые значения многих магических методов присваиваются объектам Php :: Value, а это означает, что до тех пор, пока вы убедитесь, что ваш магический метод возвращает тип, который присваивается Php :: Value, вы можете использовать его в своем классе. Таким образом, ваш метод __toString () может возвращать char *, std :: string, Php :: Value (и даже integer!), Потому что всем этим типам может быть присвоено значение Php :: Value.

Хорошая вещь о магических методах, реализованных с PHP-CPP, заключается в том, что они не становятся видимыми из пользовательского пространства PHP. Другими словами, при определении функции типа __set () или __unset () в классе C++ эти функции не могут вызываться явным образом из PHP - скриптов, но они вызываются при обращении к свойству.

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

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

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

Другая причина, по которой вам необходимо явно зарегистрировать метод __construct (), заключается в том, что в отличие от других магических методов метод __construct должен быть видимым с PHP. Внутри конструкторов производных классов часто приходится обращаться к parent :: __ construct (). Регистрируя метод __construct () в функции get_module (), вы делаете функцию видимой с PHP.

У нас есть специальная статья о конструкторах и деструкторах с несколькими примерами, как зарегистрировать метод __construct ().

Клонирование и деструкторы

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

Магический метод __destruct () вызывается непосредственно перед тем, как объект будет разрушен (и прямо перед тем, как запускается деструктор C ++).

Методы __clone () и __destruct () являются обычными магическими методами (в отличие от __construct ()), поэтому вам не нужно регистрировать их, чтобы они были активными. Если вы добавите один из этих двух методов в свой класс, вам не придется вносить какие-либо изменения в функцию запуска get_module (). Библиотека PHP-CPP называет их автоматически, если они доступны.

В обычных условиях вам, вероятно, не нужно этих методов. Также можно использовать конструктор копии C ++ и деструктор C ++. Единственное различие заключается в том, что магические методы вызывают объекты, которые находятся в полностью инициализированном состоянии, а конструктор копии C ++ и деструктор C ++ работают с объектами, которые инициализируются или разрушаются.

В статье об упомянутых выше конструкторах и деструкторах есть более подробные сведения и примеры.

Псевдо свойства

С помощью методов __get (), __set (), __unset () и __isset () вы можете определить псевдо свойства. Это позволяет вам, например, создавать свойства только для чтения или свойства, которые проверяются на достоверность, когда они установлены.

Магические методы работают точно так же, как их аналоги в PHP-скриптах, поэтому вы можете легко переносить PHP-код, который использует эти свойства для C ++.

#include <phpcpp.h>

/**
 *  Класс sample, который имеет некоторые псевдо свойства, которые сопоставляются с родными типами
 */
class User : public Php::Base
{
private:
    /**
     *  Name of the user
     *  @var    std::string
     */
    std::string _name;

    /**
     *  Адрес электронной почты пользователя
     *  @var    std::string
     */
    std::string _email;

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

    /**
     *  Получить доступ к свойству
     *  @param  name        Name of the property
     *  @return Value       Property value
     */
    Php::Value __get(const Php::Value &name)
    {
        // провермть, поддерживается ли имя свойства
        if (name == "name") return _name;
        if (name == "email") return _email;

        // свойство не поддерживается, отступить по умолчанию
        return Php::Base::__get(name);
    }

    /**
     *  Перезаписать свойство
     *  @param  name        Name of the property
     *  @param  value       New property value
     */
    void __set(const Php::Value &name, const Php::Value &value) 
    {
        // проверить имя свойства
        if (name == "name") 
        {
            // член магазина
            _name = value.stringValue();
        }

        // проверяем электронную почту на действительность
        else if (name == "email")
        {
            // сохранение электронной почты в строку
            std::string email = value;

            // должен содержать символ"@"
            if (email.find('@') == std::string::npos) 
            {
                // адрес электронной почты является недопустимым, бросить исключение
                throw Php::Exception("Invalid email address");
            }

            // сохранить элемент
            _email = email;
        }

        // другие свойства возвращаются к свойствам по умолчанию
        else
        {
            // вызов по умолчанию
            Php::Base::__set(name, value);
        }
    }

    /**
     *  Проверить, установлено ли свойство
     *  @param  name       Имя свойства
     *  @return bool
     */
    bool __isset(const Php::Value &name) 
    {
        // true для имени и адреса электронной почты
        if (name == "name" || name == "email") return true;

        // возврат к умолчанию
        return Php::Base::__isset(name);
    }

    /**
     *  Удаление свойства
     *  @param  name        Name of the property to remove
     */
    void __unset(const Php::Value &name)
    {
        // имя и адрес электронной почты не могут быть отменены
        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");
        }

        // возврат к умолчанию
        Php::Base::__unset(name);
    }
};

/**
 *  Переключить в контекст 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<User> user("User");

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

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

В приведенном выше примере показано, как создать класс пользователя, который, как представляется, имеет имя и свойство электронной почты, но это не позволяет вам назначать адрес электронной почты без символа «@», и это не позволяет удалить свойства.

<?php
// инициализировать пользователя и установить его имя и адрес электронной почты
$user = new User();
$user->name = "John Doe";
$user->email = "john.doe@example.com";

// показать адрес электронной почты
echo($user->email."\n");

// удалить адрес электронной почты (это вызовет исключение)
unset($user->email);
?>

Волшебные методы __call (), __callStatic () и __invoke ()

Методы C ++ должны быть зарегистрированы явно в вашей функции get_module () расширения, чтобы быть доступной из пространства пользователя PHP. Однако, когда вы переопределяете метод __call (), вы можете принимать все вызовы - даже вызовы методов, которые не существуют. Когда кто-то делает вызов из пользовательского пространства на что-то похожее на метод, он будет передан этому методу __call (). В сценарии вы можете использовать $object-> something (), $object-> whatever () или $object-> anything () - неважно, что такое имя метода, все эти вызовы передаются метод __call () в классе C ++.

Метод __callStatic () аналогичен методу __call (), но работает для статических методов. Статический вызов YourClass :: someMethod () может быть автоматически передан методу __callStatic () вашего класса C ++.

Рядом с магическими функциями __call () и __callStatic библиотека PHP-CPP также поддерживает метод __invoke (). Это метод, который вызывается, когда экземпляр объекта используется так, как если бы он был функцией. Это можно сравнить с перегрузкой оператора () в классе C ++. Внедряя метод __invoke (), скрипты из пользовательского пространства PHP могут создавать объект, а затем использовать его как функцию.

#include <phpcpp.h>

/**
 *  Класс образца, который принимает все мыслимые вызовы метода
 */
class MyClass : public Php::Base
{
public:
    /**
     *  Конструктор C++ и деструктпр C++
     */
    MyClass() = default;
    virtual ~MyClass() = default;

    /**
     *  Regular method
     *  @param  params      Параметры, переданные в метод
     *  @return Value       Значение
     */
    Php::Value regular(Php::Parameters &params)
    {
        return "this is a regular method";
    }

    /**
     *  Переопределить метод __call () для принятия всех вызовов метода
     *  @param  name        Имя метода, который вызывается
     *  @param  params      Параметры, переданные методу
     *  @return Value       Возвращаемое значение
     */
    Php::Value __call(const char *name, Php::Parameters &params)
    {
        // возвращаемое значение
        std::string retval = std::string("__call ") + name;

        // циклический перебор параметров
        for (auto &param : params)
        {
            // добавить значение строки параметра для возврата значения
            retval += " " + param.stringValue();
        }

        // сделанный
        return retval;
    }

    /**
     *  Переопределить метод __callStatic() для принятия всех вызовов статических методов
     *  @param  name        Имя метода, который называется
     *  @param  params      Параметры, переданные методу
     *  @return Value       Возвращаемое значение
     */
    static Php::Value __callStatic(const char *name, Php::Parameters &params)
    {
        // возвращаемое значение
        std::string retval = std::string("__callStatic ") + name;

        // циклический перебор параметров
        for (auto &param : params)
        {
            // добавить строковое значение параметра к возвращаемому значению
            retval += " " + param.stringValue();
        }

        // сделанный
        return retval;
    }

    /**
     *  Переопределенный метод __invoke (), чтобы объекты вызывались напрямую
     *  @param  params      Параметры, переданные методу
     *  @return Value       Возвращаемое значение
     */
    Php::Value __invoke(Php::Parameters &params)
    {
        // возвращаемое значение
        std::string retval = "invoke";

        // циклический перебор параметров
        for (auto &param : params)
        {
            // добавить строковое значение параметра к возвращаемому значению
            retval += " " + param.stringValue();
        }

        // сделанный
        return retval;
    }

};

/**
 *  Переключить на 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<MyClass> myClass("MyClass");

        // зарегистрировать метод regular
        myClass.method<&MyClass::regular>("regular");

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

        // вернуть расширение
        return myExtension;
    }
}
<?php
// инициализировать объект
$object = new MyClass();

// вызвать обычный метод
echo($object->regular()."\n");

// вызвать некоторые псевдо-методы
echo($object->something()."\n");
echo($object->myMethod(1,2,3,4)."\n");
echo($object->whatever("a","b")."\n");

// вызывать некоторые псевдо-методы в статическом контексте
echo(MyClass::something()."\n");
echo(MyClass::myMethod(5,6,7)."\n");
echo(MyClass::whatever("x","y")."\n");

// вызов объекта, как если бы он был функцией
echo($object("parameter","passed","to","invoke")."\n");
?>

Вышеупомянутый PHP-скрипт вызывает некоторый метод для этого класса. И будет генерировать следующий результат:

regular
__call something
__call myMethod 1 2 3 4
__call whatever a b
__callStatic something
__callStatic myMethod 5 6 7
__callStatic whatever x y
invoke parameter passed to invoke

Просмотр в строку

В PHP вы можете добавить метод __toString () в класс. Этот метод автоматически вызывается, когда объект передается в строку или когда объект используется в контексте строки. PHP-CPP также поддерживает этот метод __toString ().

#include <phpcpp.h>

/**
 *  Пример класса, с методами для приведения объектов к скалярам
 */
class MyClass : public Php::Base
{
public:
    /**
     *  Конструктор C++ и деструктпр C++
     */
    MyClass() = default;
    virtual ~MyClass() = default;

    /**
     *  Приведение к строке
     *  @return Value
     */
    Php::Value __toString()
    {
        return "abcd";
    }
};

/**
 *  Переключить в контекст 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<MyClass> myClass("MyClass");

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

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

Рядом с описанными здесь магическими методами и, возможно, вы уже знаете, что при написании PHP-скриптов библиотека PHP-CPP также вводит ряд дополнительных магических методов. К ним относятся дополнительные методы литья и метод сравнения объектов.

Подробнее об этих дополнительных функциях вы можете прочитать в следующем разделе.