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

Магические методы

В отличии от C++ в PHP нет перегрузки операторов (как и самих операторов), но есть, так называемые магические методы, которые расширяют ООП возможности языка и позволяют частично компенсировать отсутсвие возможности перегрузки операторов. Речь идет о таких методах как __set(), __invoke(), __call() и так далее, которые хорошо известны PHP программистам.

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

Выявление времени компиляции

Возможно, вы ожидаете, что магические методы будут вертуальными методами в C++ классе Php::Base. Так что бы их ожно было переопределить в наследуемых классах. Однако это не так. Магические метода реализованы в PHP-CPP гараздо удобнее — это просто обычные методы в вашем C++ классе. Все что требуется, это просто нужное имя метода. Компилятор сам, по имени метода, определяет что метод является магическим.

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

Приятным отличием магических методов реализуемых в PHP-CPP, от родной реализации в PHP является то что они будучи определенными описаным выше способом не становятся видимыми из клиентского PHP кода. Другими словами, вы не сможете вызвать и напрямую таким образом: $x->__get('y'). Приэтом они выполняют свое прямое предназначение. Впрочем, если по какой то неизвестной причине, вам хочется странного, вы можете явно объявить магический метод в функции get_module() как обычный метод. В этом случае магический метод не потеряет своих "волшебных" свойств.

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

Как уже было сказано, нет необходимости явно регистрировать магические методы (такие как __toString(), __get() и тп) в функции get_module(). Исключением из этого правила является метод __construct(). Если вы добавите метод __construct() в свой C++ класс он не будет автоматически зарегистрирован в вашем PHP классе на этапе компиляции. Для этого есть несколько причин. Во первых метод __construct() не имеет фиксированной сигнатуры. Он может быть определен вообще без параметров. Или с различным числом аргументом. Кроме того, в разных ситуациях может быть необходимо разную область видимости метода __construct(). Он может быть определен как public, private или protected.

И наконец, в отличии от магических методов, метод __construct() действительно иногда необходимо явно вызывать. Например, из конструктора производного класса: parent::__construct(). По этой причине вы должны, если это необходимо, явно регистрировать метод __construct() в get_module().

Более подробно о том как работают конструкторы см. в разделе конструкторы и деструкторы

__clone() и __destruct()

Метод __clone() очень похож на метод __construct(). Так же как и метод __construct(), который запускается после нормального C++ Конструктора, метода __clone() запускается после того как объект будет фактически скопирован, т.е. после запуска C++ копирующего конструкторы. Поскольку метод __clone() имеет фиксированную сигнатуру, есть возможность встроить его на этапе кмпиляции без явного определения в get_module(). Вам не нужно объявлять метод __clone() в своем C++ коде, а вместо него достаточно (явно или неяно) определить копирующий конструктор. В этом случайе ваш класс будет клонируемым и для его клонирования будет использован ваш копирующий конструктор.

Если вам нужно ограничить область видимости метода get_module(), вы можете явно объявить его в get_module(). Более подробно о клонировании объектов см. в разделе конструкторы и деструкторы.

Метод __destruct() вызывается прямо перед фактическим разрушением объекта. Т.е. перед вызовом C++ деструктора. Как и метод __clone() вам не нужно явно регистрировать метод __destruct(). По большому счету, все действия, необходимые для разрушения объекта должны быть описаны в C++ деструкторе и нет никакой необъодимости в __destruct().

Мнимые поля класса

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

Магические методы PHP-CPP работают вточности также как их PHP аналоги. Так что вы можете без проблем портировать PHP код, использующий магические методы из PHP в PHPCPP.

Приведем небольшой пример, демонстрирующий работу волшебных методов в PHP-CPP:

#include <phpcpp.h>

/**
 *  A sample class, that has some pseudo properties that map to native types
 */
class User : public Php::Base
{
private:
    /**
     *  Name of the user
     *  @var    std::string
     */
    std::string _name;
    
    /**
     *  Email address of the user
     *  @var    std::string
     */
    std::string _email;

public:
    /**
     *  C++ constructor and C++ destructpr
     */
    User() {}
    virtual ~User() {}

    /**
     *  Get access to a property
     *  @param  name        Name of the property
     *  @return Value       Property value
     */
    Php::Value __get(const Php::Value &name)
    {
        // check if the property name is supported
        if (name == "name") return _name;
        if (name == "email") return _email;
        
        // property not supported, fall back on default
        return Php::Base::__get(name);
    }
    
    /**
     *  Overwrite a property
     *  @param  name        Name of the property
     *  @param  value       New property value
     */
    void __set(const Php::Value &name, const Php::Value &value) 
    {
        // check the property name
        if (name == "name") 
        {
            // store member
            _name = value.stringValue();
        }
        
        // we check emails for validity
        else if (name == "email")
        {
            // store the email in a string
            std::string email = value;
            
            // must have a '@' character in it
            if (email.find('@') == std::string::npos) 
            {
                // email address is invalid, throw exception
                throw Php::Exception("Invalid email address");
            }
            
            // store the member
            _email = email;
        }
        
        // other properties fall back to default
        else
        {
            // call default
            Php::Base::__set(name, value);
        }
    }
    
    /**
     *  Check if a property is set
     *  @param  name        Name of the property
     *  @return bool
     */
    bool __isset(const Php::Value &name) 
    {
        // true for name and email address
        if (name == "name" || name == "email") return true;
        
        // fallback to default
        return Php::Base::__isset(name);
    }
    
    /**
     *  Remove a property
     *  @param  name        Name of the property to remove
     */
    void __unset(const Php::Value &name)
    {
        // name and email can not be unset
        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");
        }
        
        // fallback to default
        Php::Base::__unset(name);
    }
};

/**
 *  Switch to C context to ensure that the get_module() function
 *  is callable by C programs (which the Zend engine is)
 */
extern "C" {
    /**
     *  Startup function that is called by the Zend engine 
     *  to retrieve all information about the extension
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() {
        
        // extension object
        static Php::Extension myExtension("my_extension", "1.0");
        
        // description of the class so that PHP knows 
        // which methods are accessible
        Php::Class<User> user("User");
        
        // add the class to the extension
        myExtension.add(std::move(user));
        
        // return the extension
        return myExtension;
    }
}

В приведенном примере мы определи класс User. Со стороны клиентского кода он выглядит так, как будто в нем определны два поля name и email. При этом при присвоении полю email значения автоматически происходи проверка на наличие в присваиваемой строке символа '@'. Кроме того клиентскому коду запрещено удалять поля name и email.

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

// show the email address
echo $user->email, PHP_EOL;

// remove the email address (this will cause an exception)
unset($user->email);
?>

Обратите внимание на вызовы вида Php::Base::__unset():

void __unset(const Php::Value &name)
{
    //...
    // fallback to default
    Php::Base::__unset(name);
}
Эти вызовы позволяют переадрисовать генерируемое магическим методом действие к регулярным полям. Например, вызов Php::Base::__unset('property'); приведет к удалению регулярного поля PHP-класса с именем property.

Магические методы __call(), __callStatic() и __invoke()

Магический метод __call() позволяет принимать все вызовы методов вашего оббъекта. Даже те, которые не были явно зарегистрированы в ядре PHP в функции get_module() т.е. даже такие методы, которых с точки зрения клиентского кода не должно существовать. Если вы определите метод __call() в вашем C++ классе, то он будет принимать все несуществующие методы.

Метод __callStatic() работает аналогичным образом с той разницей, что он перехватывает вызовы к несуществующим статическим методам класса.

Наконец, если вы определите в своем С++ классе метод __invoke(), то это превратит объект вашего PHP-класса в функтор. Т.е. объект класса иожно будет использовать как функцию.

#include <phpcpp.h>

/**
 *  A sample class, that accepts all thinkable method calls
 */
class MyClass : public Php::Base
{
public:
    /**
     *  C++ constructor and C++ destructpr
     */
    MyClass() {}
    virtual ~MyClass() {}

    /**
     *  Regular method
     *  @param  params      Parameters that were passed to the method
     *  @return Value       The return value
     */
    Php::Value regular(Php::Parameters &params)
    {
        return "это обычный метод";
    }

    /**
     *  Overriden __call() method to accept all method calls
     *  @param  name        Name of the method that is called
     *  @param  params      Parameters that were passed to the method
     *  @return Value       The return value
     */
    Php::Value __call(const char *name, Php::Parameters &params)
    {
        // the return value
        std::string retval = std::string("__call ") + name;
        
        // loop through the parameters
        for (auto &param : params)
        {
            // append parameter string value to return value
            retval += " " + param.stringValue();
        }
        
        // done
        return retval;
    }

    /**
     *  Overriden __callStatic() method to accept all static method calls
     *  @param  name        Name of the method that is called
     *  @param  params      Parameters that were passed to the method
     *  @return Value       The return value
     */
    static Php::Value __callStatic(const char *name, Php::Parameters &params)
    {
        // the return value
        std::string retval = std::string("__callStatic ") + name;
        
        // loop through the parameters
        for (auto &param : params)
        {
            // append parameter string value to return value
            retval += " " + param.stringValue();
        }
        
        // done
        return retval;
    }

    /**
     *  Overridden __invoke() method so that objects can be called directly
     *  @param  params      Parameters that were passed to the method
     *  @return Value       The return value
     */
    Php::Value __invoke(Php::Parameters &params)
    {
        // the return value
        std::string retval = "invoke";

        // loop through the parameters
        for (auto &param : params)
        {
            // append parameter string value to return value
            retval += " " + param.stringValue();
        }
        
        // done
        return retval;
    }

};

/**
 *  Switch to C context to ensure that the get_module() function
 *  is callable by C programs (which the Zend engine is)
 */
extern "C" {
    /**
     *  Startup function that is called by the Zend engine 
     *  to retrieve all information about the extension
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() {
        
        // extension object
        static Php::Extension myExtension("my_extension", "1.0");
        
        // description of the class so that PHP knows 
        // which methods are accessible
        Php::Class<MyClass> myClass("MyClass");
        
        // register the regular method
        myClass.method("regular", &MyClass::regular);
        
        // add the class to the extension
        myExtension.add(std::move(myClass));
        
        // return the extension
        return myExtension;
    }
}

<?php
// initialize an object
$object = new MyClass();

// call a regular method
echo $object->regular(), PHP_EOL;

// call some pseudo-methods
echo $object->something(), PHP_EOL;
echo $object->myMethod(1,2,3,4), PHP_EOL;
echo $object->whatever("a","b"), PHP_EOL;

// call some pseudo-methods in a static context
echo MyClass::something(), PHP_EOL;
echo MyClass::myMethod(5,6,7), PHP_EOL;
echo MyClass::whatever("x","y"), PHP_EOL;

// call the object as if it was a function
echo $object("parameter","passed","to","invoke"), PHP_EOL;
?>

Приведенный выше скрипт даст следующий вывод:

это обычный метод
__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

Преобразование объекта в строку

Метод __toString(), если он был определенв PHP классе, вызывается когда объект преобразуется в строку. PHPCPP также поддерживает этот магический метод.

#include <phpcpp.h>

/**
 *  A sample class, with methods to cast objects to scalars
 */
class MyClass : public Php::Base
{
public:
    /**
     *  C++ constructor and C++ destructpr
     */
    MyClass() {}
    virtual ~MyClass() {}

    /**
     *  Cast to a string
     *  @return Value
     */
    Php::Value __toString()
    {
        return "abcd";
    }
};

/**
 *  Switch to C context to ensure that the get_module() function
 *  is callable by C programs (which the Zend engine is)
 */
extern "C" {
    /**
     *  Startup function that is called by the Zend engine 
     *  to retrieve all information about the extension
     *  @return void*
     */
    PHPCPP_EXPORT void *get_module() {
        
        // extension object
        static Php::Extension myExtension("my_extension", "1.0");
        
        // description of the class so that PHP knows 
        // which methods are accessible
        Php::Class<MyClass> myClass("MyClass");
        
        // add the class to the extension
        myExtension.add(std::move(myClass));
        
        // return the extension
        return myExtension;
    }
}

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

Более подробно об этих дополнительных возможностях можно узнать в раздеое Специальные возможности.

Установка PHP-CPP Загрузка расширений Ваше первое расширение Вывода и ошибок Функции Параметры Вызов функций и методов Классы и объекты Конструкторы и деструкторы Наследование Магические методы Магические интерфейсы Генерация исключений Специальные возможности Поля классов Работа с переменными ini записи Extension callbacks Пространства имен