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

Магические интерфейсы

PHP включает несколько, так называемых, магических интерфейсов позволяющих расширить возможности реализующих их классов. Подобно магическим методам, эти интерфейсы расширяют возможности языка. Речь идет о таких интерфейсах, как Countable, ArrayAccess, Serializable и т.п. Библиотека PHP-CPP позволяет вам также использовать эти интерфейсы и возможности, которые они дают.

Кажется странным, почему в PHP в некоторых случаях для изменения поведения класса используются магическим методы, а в некоторых магические интерфейсы. По большому счету, магические методы и интерфейсы выполняют одну и туже задачу и нет никаких причин их разделять, лишая тем самым язык единообразия. На наш взгяд, интерфейс Serializable, например, вполне мог бы быть заменен магическими методами __serialize() и __unserialize(). Или магический метод __invoke() вполне мог бы быть заменен магическим интерфейсом Invokable. PHP — это не стандартизированный язык. И некоторые вещи в нем реализованы так как кому-то из разработчиков на душу легло.

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

Поддержка SPL интерфейсов

Стандартная установка PHP поставляется с включенной в нее библиотекой SPL (Standard PHP Library). Это расширение, которое построено с использованием возможностей Zend-API для создания классов и интерфейсов расширяющих возможности языка.

PHP-CPP библиотека также имеет интерфейсы с этими именами, и они ведут себя в более или менее так же, как и SPL интерфейсов. Но внутренне, PHP, CPP библиотека не зависит от SPL. Если вы реализуете C интерфейсом как Php::ArrayAccess или Php::зачетная, это нечто другое, чем писать класс в PHP, реализующий SPL интерфейс. Библиотека PHP-CPP так же включает в себя "интерфейсы" с теми же именами что и SPL и дающие, в определенной, степени тоже поведение для реализующих их классов что и интерфейсы SPL. Но внутри PHP-CPP не включает в себя SPL и никаким образом от него не зависит.

И PHP-CPP и SPL оба используют одни и те же возможности Zend-API, предлагают одни и те же возможности для классов реализующих соответствующие интерфейсы, но оба не зависят друг от друга. Таким образом, если расширение SPL выгружено в вашей сборке PHP, вы можете продолжать использовать его возможности с помощью библиотеки PHP-CPP.

Интерфейс Countable

Реализуя интерфейс Php::Countable вы можете создавать объекты, которые может принимать в качестве аргумента PHP функция count().

#include <phpcpp.h>

/**
 *  The famous counter class, now also implements 
 *  the Php::Countable interface
 */
class Counter : public Php::Base, public Php::Countable
{
private:
    /**
     *  The internal counter value
     *  @var int
     */
    int _value = 0;

public:
    /**
     *  C++ constructor and C++ destructor
     */
    Counter() {}
    virtual ~Counter() {}
    
    /**
     *  Methods to increment and decrement the counter
     */
    Php::Value increment() { return ++_value; }
    Php::Value decrement() { return --_value; }

    /**
     *  Method from the Php::Countable interface, that
     *  is used when a Counter instance is passed to the
     *  PHP count() function
     *  
     *  @return long
     */
    virtual long count() override { return _value; }
};

/**
 *  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<Counter> counter("Counter");
        
        // add methods
        counter.method("increment", &Counter::increment);
        counter.method("decrement", &Counter::decrement);
        
        // add the class to the extension
        myExtension.add(std::move(counter));
        
        // return the extension
        return myExtension;
    }
}

Класс Php::Countable содержит один чисто виртуальный метод count(), который должен быть реализован в производном классе:

class Countable
{
public:
    /**
     *  Получить количество элементов в классе
     *  @return long
     */
    virtual long count() = 0;
};
Нет необходимости регистрировать специальные функции в get_module(). Все что вам нужно сделать, что бы получить функциональность Countable, это объявить свой класс производным от Php::Countable и реализовать виртуальный метод count() в своем классе.

<?php
// create a counter
$counter = new Counter();
$counter->increment();
$counter->increment();
$counter->increment();

// show the current value
echo(count($counter)."\n");
?>
Как и ожидается результат будет: 3

Интерфейс ArrayAccess

Интерфейс обеспечивает доступ к объектам как к массиву. Для того что бы ваш класс получил функциональность, предоставляемую этим "магическим интерфейсом" он должен быть производным от Php::ArrayAccess и реализовать его абстрактные методы:

class ArrayAccess
{
public:
    /**
     *  Определяет, существует ли заданное смещение (ключ)
     *  @param  key
     *  @return bool
     */
    virtual bool offsetExists(const Php::Value &key) = 0;
    
    /**
     *  Устанавливает заданное смещение (ключ)
     *  @param  key
     *  @param  value
     */
    virtual void offsetSet(const Php::Value &key, const Php::Value &value) = 0;
    
    /**
     *  Возвращает заданное смещение (ключ)
     *  @param  key
     *  @return value
     */
    virtual Php::Value offsetGet(const Php::Value &key) = 0;
    
    /**
     *  Удаляет смещение (ключ)
     *  @param key
     */
    virtual void offsetUnset(const Php::Value &key) = 0;
};

Следующий пример иллючтрирует совместное использование Php::Countable и Php::ArrayAccess:

#include <phpcpp.h>

/**
 *  A sample Map class, that can be used to map string-to-strings
 */
class Map : public Php::Base, public Php::Countable, public Php::ArrayAccess
{
private:
    /**
     *  Internally, a C++ map is used
     *  @var    std::map<std::string,std::string>
     */
    std::map<std::string,std::string> _map;

public:
    /**
     *  C++ constructor and C++ destructpr
     */
    Map() {}
    virtual ~Map() {}
    
    /**
     *  Method from the Php::Countable interface that 
     *  returns the number of elements in the map
     *  @return long
     */
    virtual long count() override 
    { 
        return _map.size(); 
    }

    /**
     *  Method from the Php::ArrayAccess interface that is
     *  called to check if a certain key exists in the map
     *  @param  key
     *  @return bool
     */
    virtual bool offsetExists(const Php::Value &key) override
    {
        return _map.find(key) != _map.end();
    }
    
    /**
     *  Set a member
     *  @param  key
     *  @param  value
     */
    virtual void offsetSet(const Php::Value &key, const Php::Value &value) override
    {
        _map[key] = value.stringValue();
    }
    
    /**
     *  Retrieve a member
     *  @param  key
     *  @return value
     */
    virtual Php::Value offsetGet(const Php::Value &key) override
    {
        return _map[key];
    }
    
    /**
     *  Remove a member
     *  @param key
     */
    virtual void offsetUnset(const Php::Value &key) override
    {
        _map.erase(key);
    }
};

/**
 *  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<Map> map("Map");
        
        // add the class to the extension
        myExtension.add(std::move(map));
        
        // return the extension
        return myExtension;
    }
}

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

<?php
// create a map
$map = new Map();

// store some values
$map["a"] = 1234;
$map["b"] = "xyz";
$map["c"] = 0;

// show the values
echo $map["a"], PHP_EOL;;
echo $map["b"], PHP_EOL;;
echo $map["c"], PHP_EOL;;

// access a value that does not exist
echo $map["d"], PHP_EOL;;

// this will result in a fatal error,
// the ArrayAccess methods are not exported to user space
echo $map->offsetGet("a"), PHP_EOL;;

?>

Интерфейс Traversable

Что бы ваш класс был итерируемым в пользовательском пространстве, т.е. что бы для него было возможно использовать цикл foreach так же как и для обычных php-массивов вы можете унаследовать его от C++ "интерфейса" Php::Traverable:

class Traversable
{
public:
    /**
     *  Retrieve an instance of the iterator
     *  @return Iterator
     */
    virtual Iterator* getIterator() = 0;
};

Реализация итераторов в PHP-CPP немного отличается от того как они реализованы в SPL. И, соответственно, от того как они могут использоваться в PHP. В php что бы сделать класс итерируемым (т.е. что бы объекты этого класса можно было использовать в циклах foreach словно массивы), вы должны реализовать один из интерфейсов: либо Iterator, либо IteratorAggregate. Это, мягко говоря, весьма своеобразное архетектурное решение. Если вы звдумаетесь над этим вопросом, то увидите, что на самом деле итерируемый объект никогда не должен сам являться итератором. Но итератор, на самом деле, это отдельная сущьность, которую сам объект использует для приобретения определенного поведения. Рассмотрим пример:

<?php
// fill a map
$map = new Map();
$map["a"] = 1234;
$map["b"] = 5678;

// iterate over it
foreach ($map as $key => $value)
{
    // output the key and value
    echo("$key: $value\n");
}
?>
Здесь $map на самом деле вовсе не итератор, а лишь контейнер, по верх которого происходит итерирование. Настоящий же итератор скрыт от глаз пользователя. В SPL, однако $map был бы так же назван итератором.

Класс Php::Iterator так же имеется в PHP-CPP, но он не будет доступен в пользовательских php скриптах. Это вспомогательный класс, который может быть использован внутри вашего расширения для того что бы придать классу, реализующему Php::Traverable свойство итерируемости. метод Php::Traverable::getIterator() возвращает указатель на класс реализующий Php::Iterator.

class Iterator
{
public:
    /**
     *  Конструктор
     *  @param  base        Класс, для которого реализуется итератор
     */
    Iterator(Base *base) : _object(base) {}
    
    /**
     *  Destructor
     */
    virtual ~Iterator() {}
    
    /**
     *  Проверка корректности позиции
     *  @return bool
     */
    virtual bool valid() = 0;
    
    /**
     *  Возвращает текущий элемент
     *  @return Value
     */
    virtual Value current() = 0;
    
    /**
     *  Возвращает ключ текущего элемента
     *  @return Value
     */
    virtual Value key() = 0;
    
    /**
     *  Переход к следующему элементу
     */
    virtual void next() = 0;
    
    /**
     *  Сбрасывает (устанавливает) позицию итератора к первому элементу
     */
    virtual void rewind() = 0;
    
protected:
    /**
     *  Пока существует итератор, объект для которого он создан, хранится в отдельной переменной.
     *  Это сделано для того, что бы гарантировать,
     *  что объект не будет разрушен до тех пор, пока существует итератор.
     *  @var    Value
     */
    Value _object;
    
};

Ниже представлен пример, демонстрирующий использование Php::Traverable:

#include <phpcpp.h>

/**
 *  A sample iterator class that can be used to iterate
 *  over a map of strings
 */
class MapIterator : public Php::Iterator
{
private:
    /**
     *  The map that is being iterated over
     *  This is a reference to the actual map
     *  @var    std::map<std::string,std::string>
     */
    const std::map<std::string,std::string> &_map;
    
    /**
     *  The actual C++ iterator
     *  @var    std::map<std::string,std::string>l;::const_iterator;
     */
    std::map<std::string,std::string>::const_iterator _iter;

public:
    /**
     *  Constructor
     *  @param  object      The object that is being iterated over
     *  @param  map         The internal C++ map that is being iterated over
     */
    MapIterator(Map *object, const std::map<std::string,std::string> &map) :
        Php::Iterator(object), _map(map), _iter(map.begin()) {}
        
    /**
     *  Destructor
     */
    virtual ~MapIterator() {}
    
    /**
     *  Is the iterator on a valid position
     *  @return bool
     */
    virtual bool valid() override
    {
        return _iter != _map.end();
    }
    
    /**
     *  The value at the current position
     *  @return Value
     */
    virtual Php::Value current() override
    {
        return _iter->second;
    }
    
    /**
     *  The key at the current position
     *  @return Value
     */
    virtual Php::Value key() override
    {
        return _iter->first;
    }
    
    /**
     *  Move to the next position
     */
    virtual void next() override
    {
        _iter++;
    }
    
    /**
     *  Rewind the iterator to the front position
     */
    virtual void rewind() override
    {
        _iter = _map.begin();
    }
};


/**
 *  A sample Map class, that can be used to map string-to-strings
 */
class Map : 
    public Php::Base, 
    public Php::Countable, 
    public Php::ArrayAccess,
    public Php::Traversable
{
private:
    /**
     *  Internally, a C++ map is used
     *  @var    std::map<std::string,std::string>
     */
    std::map<std::string,std::string> _map;

public:
    /**
     *  C++ constructor and C++ destructpr
     */
    Map() {}
    virtual ~Map() {}
    
    /**
     *  Method from the Php::Countable interface that 
     *  returns the number of elements in the map
     *  @return long
     */
    virtual long count() override 
    { 
        return _map.size(); 
    }

    /**
     *  Method from the Php::ArrayAccess interface that is
     *  called to check if a certain key exists in the map
     *  @param  key
     *  @return bool
     */
    virtual bool offsetExists(const Php::Value &key) override
    {
        return _map.find(key) != _map.end();
    }
    
    /**
     *  Set a member
     *  @param  key
     *  @param  value
     */
    virtual void offsetSet(const Php::Value &key, const Php::Value &value) override
    {
        _map[key] = value.stringValue();
    }
    
    /**
     *  Retrieve a member
     *  @param  key
     *  @return value
     */
    virtual Php::Value offsetGet(const Php::Value &key) override
    {
        return _map[key];
    }
    
    /**
     *  Remove a member
     *  @param key
     */
    virtual void offsetUnset(const Php::Value &key) override
    {
        _map.erase(key);
    }
    
    /**
     *  Get the iterator
     *  @return Php::Iterator
     */
    virtual Php::Iterator *getIterator() override
    {
        // construct a new map iterator on the heap
        // the (PHP-CPP library will delete it when ready)
        return new MapIterator(this, _map);
    }
};

/**
 *  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<Map> map("Map");
        
        // add the class to the extension
        myExtension.add(std::move(map));
        
        // return the extension
        return myExtension;
    }
}

В приведенном примере мы сделали наш класс Map производным от Php::Traversable и реализовали его абстрактный метод getIterator(). Этот метод возвращает указатель на класс MapIterator который мы разместили в куче. Не стоит беспокоиться об управлении памятью. Биюлиотека PHP-CPP разрушит объект итератора и освободит память как только цикл foreach будет завершен.

Класс MapIterator, из приведенного выше примера, является производным от Php::Iterator, и реализует все его абстрактные методы. Реализация этих методов необходима для работы цикла foreach. Обратите внимание, что класс Php::Iterator ожидает, что в конструктор будет передан указатель на объект, над которым он проходит. Это необходимо, что бы обеспечить сущиствование объекта над которым производится итерирование до тех пор, пока существует итератор (до тех пор, пока цикл foreach не пройден).

Интерфейс Serializable

Что бы ваш php класс реализовал интерфейс Serializable, ваш C++ класс в расширении PHP-CPP должен быть производным от Php::Serializable.

class Serializable
{
public:
    /**
     *  Сериализует объект в строку
     *  @return std::string
     */
    virtual std::string serialize() = 0;
    
    /**
     *  Создает PHP-объект из сериализованной строки
     *  Вызывается в качестве альтернативы PHP конструктору __construct()
     *  при инициализации объекта.
     *
     *  @param  input           Сериализованная строка
     *  @param  size            Размер строки в байтах
     */
    virtual void unserialize(const char *input, size_t size) = 0;
};

Пример использования:

#include <phpcpp.h>

/**
 *  Counter class that can be used for counting
 */
class Counter : public Php::Base, public Php::Serializable
{
private:
    /**
     *  The initial value
     *  @var    int
     */
    int _value = 0;

public:
    /**
     *  C++ constructor and destructor
     */
    Counter() {}
    virtual ~Counter() {}
    
    /**
     *  Update methods to increment or decrement the counter
     *  Both methods return the NEW value of the counter
     *  @return int
     */
    Php::Value increment() { return ++_value; }
    Php::Value decrement() { return --_value; }
    
    /**
     *  Method to retrieve the current counter value
     *  @return int
     */
    Php::Value value() const { return _value; }
    
    /**
     *  Serialize the object into a string
     *  @return std::string
     */
    virtual std::string serialize() override
    {
        return std::to_string(_value);
    }
    
    /**
     *  Unserialize the object from a string
     *  @param  buffer
     *  @param  size
     */
    virtual void unserialize(const char *buffer, size_t size) override
    {
        _value = ::atoi(buffer);
    }
};

/**
 *  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() {
        // create static instance of the extension object
        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("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;
    }
}

Обратите внимание, что если объект воссоздается с помощью unserialize(), то метод __construct() не будет вызыван.

<?php
// create an empty counter and increment it a few times
$counter = new Counter();
$counter->increment();
$counter->increment();

// turn the counter into a storable string
$serializedCounter = serialize($counter);

// revive the counter back into an object
$revivedCounter = unserialize($serializedCounter);

// show the counter value
echo $revivedCounter->value(), PHP_EOL;
?>

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