Реализация классов в PHP5 экстеншене Часть 4 (заключительная), приложение
Ну, вот, наверно подошла очередь собрать все вместе (или хотя-бы отдельные части) и написать, что-то похожее на функциональный экстеншен.
Сейчас я работаю над созданием, экстеншена, для кеширования zval объектов (аля memcache) посему, немножко давнгрейдив код, разберем, как реализовать кэш с минимальными усилиями и небольшим функционалом.
Исходный код
Основное положения экстеншена:
- реализация паттерна singleton
- перегрузка 'magic' методов __get,__set,__isset,__unset
- работа с api HashTable
- сериализация zval
Проект состоит из основных файлов:
- php_cache.c - тут определены функции и структуры, самого экстеншена (особого вимиания он не заслуживает
)
- cache_class.c - собственно это определения методов класса, таблицы функций и есть одно интересное понятие, которое не использовалось в предыдущих частях, а именно, определение параметров метода (это особенно актуально, когда вы перегружаете какой-нибудь метод)
Собственно, если посмотреть, исходники zend_api.h, то кричащие названия параметров ZEND_BEGIN_ARG_INFO_EX, говорят сами за себя, но на всякий случай поясню (по крайней мере как я понял и что понял, надеюсь меня поправят, если что
)
name - имя которое в дальнейшие будет использоваться в том числе и добавлении метода, в таблицу методов класса,
pass_rest_by_reference - (поправьте плиз )
return_reference - результат будет возвращен как ссылка
required_num_args - количество обязательных аргументов
Далее ZEND_ARG_INFO, тут все просто, первый параметр, аргумент является ссылка на объект, второй параметр имя аргумента.
Следующая остановка, это реализация паттерна singleton:
Первая его часть, определение статического свойства класса
Вторая часть, определение статического метода instance в таблице методов класса
Третья часть, определение самого метода instance
Четвертую часть, переопределение __clone, я выкинул (это-же лишь пример реализации
)
Собственно, что нас еще интересует в cache_class.c, так это таблица методов, где мы перегружаем "magic" методы.
Собственно осталось посмотреть как определены сами методы класса cache_class.c.
- cache_ctrl - тут определены функции для работы с кешем (cache_init,cache_destroy,cache_get,cache_set,cache_isset,cache_unset)
Остановимся на основном:
Инициализация HashTable, интересным здесь является, определения callback функции cache_zval_dtor, которая будет вызываться при удалении объект из HashTable, и предпоследний параметр "1", говорит о том, что память будет выделяться не с помощью emalloc, а malloc, что для нас очень существенно, потому как, нам надо хранить переменные между запросами в противном случае уборщик мусора, вежливо удалит, все что мы на создавали (segmentation fault обеспечен )
и как бы основное, чтобы долго не мучатся, сохранением zval, все сложные типы array, object, просто сериализуем при записи и ансериалайзим при обращении.
Сериализация вообще-то проста, а вот при восстановлении не обошлось без казусов
изначально было вот так:
а потом стало:
Отличия собственно не существенные, но если кто попробует первый вариант, то увидит, что после вызова php_var_unserialize, указатель Z_STRVAL_P(pZval), станет на конец строки и при следующем обращении, начнет ансерелайзить мусор 
Вот и все!
Спасибо всем кто читал, может хоть кому-то пригодится.
PS: данный экстеншен можно брать только за основу, не более, он оставляет большое поле для деятельности (синхронизация и прочее).
Исходники, и откомпилированная dll для Windows (php 5.2.4) в комплекте (см. вверху).
Ну, вот, наверно подошла очередь собрать все вместе (или хотя-бы отдельные части) и написать, что-то похожее на функциональный экстеншен.
Сейчас я работаю над созданием, экстеншена, для кеширования zval объектов (аля memcache) посему, немножко давнгрейдив код, разберем, как реализовать кэш с минимальными усилиями и небольшим функционалом.
Исходный код
Основное положения экстеншена:
- реализация паттерна singleton
- перегрузка 'magic' методов __get,__set,__isset,__unset
- работа с api HashTable
- сериализация zval
Проект состоит из основных файлов:
- php_cache.c - тут определены функции и структуры, самого экстеншена (особого вимиания он не заслуживает

- cache_class.c - собственно это определения методов класса, таблицы функций и есть одно интересное понятие, которое не использовалось в предыдущих частях, а именно, определение параметров метода (это особенно актуально, когда вы перегружаете какой-нибудь метод)
PHP:
...
static
ZEND_BEGIN_ARG_INFO_EX(cache_arg_get, 0, 0, 1)
ZEND_ARG_INFO(0, param_name)
ZEND_END_ARG_INFO()
static
ZEND_BEGIN_ARG_INFO_EX(cache_arg_set, 0, 0, 2)
ZEND_ARG_INFO(0, param_name)
ZEND_ARG_INFO(0, param_value)
ZEND_END_ARG_INFO()
...

name - имя которое в дальнейшие будет использоваться в том числе и добавлении метода, в таблицу методов класса,
pass_rest_by_reference - (поправьте плиз )
return_reference - результат будет возвращен как ссылка
required_num_args - количество обязательных аргументов
Далее ZEND_ARG_INFO, тут все просто, первый параметр, аргумент является ссылка на объект, второй параметр имя аргумента.
Следующая остановка, это реализация паттерна singleton:
Первая его часть, определение статического свойства класса
PHP:
int cache_class_register(TSRMLS_D)
{
...
zend_declare_property_null(g_ClassEntry,"__classInstanace",16,ZEND_ACC_PRIVATE|ZEND_ACC_STATIC TSRMLS_CC);
...
}
PHP:
zend_function_entry class_methods[] = {
...
PHP_ME(Cache,instance,NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
...
};
PHP:
PHP_METHOD(Cache, instance)
{
zval* __classInstance;
__classInstance = zend_read_static_property(g_ClassEntry,"__classInstanace",16,1 TSRMLS_CC);
if(Z_TYPE_P(__classInstance) == IS_NULL)
{
// Впринципе понятно, что если объект не создан, то создаем его
Z_TYPE_P(return_value) = IS_OBJECT;
object_init_ex(return_value,g_ClassEntry);
return_value->refcount = 1;
return_value->is_ref = 1;
zend_update_static_property(g_ClassEntry,"__classInstanace",16,return_value TSRMLS_CC);
return;
}
RETURN_ZVAL(__classInstance,NULL,NULL);
}

Собственно, что нас еще интересует в cache_class.c, так это таблица методов, где мы перегружаем "magic" методы.
PHP:
zend_function_entry class_methods[] = {
...
PHP_ME(Cache,__set,cache_arg_set, ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED)
PHP_ME(Cache,__get,cache_arg_get, ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED)
PHP_ME(Cache,__isset,cache_arg_get, ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED)
PHP_ME(Cache,__unset,cache_arg_get, ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED)
...
};
- cache_ctrl - тут определены функции для работы с кешем (cache_init,cache_destroy,cache_get,cache_set,cache_isset,cache_unset)
Остановимся на основном:
Инициализация HashTable, интересным здесь является, определения callback функции cache_zval_dtor, которая будет вызываться при удалении объект из HashTable, и предпоследний параметр "1", говорит о том, что память будет выделяться не с помощью emalloc, а malloc, что для нас очень существенно, потому как, нам надо хранить переменные между запросами в противном случае уборщик мусора, вежливо удалит, все что мы на создавали (segmentation fault обеспечен )
PHP:
int cache_init(int default_cache_size TSRMLS_DC)
{
return zend_hash_init_ex(CO, default_cache_size, NULL, (dtor_func_t)cache_zval_dtor, 1, 0);
}
Сериализация вообще-то проста, а вот при восстановлении не обошлось без казусов
PHP:
static inline zval* cache_zval_serialize(zval *pZval TSRMLS_DC)
{
...
default:
{
php_serialize_data_t value_hash;
smart_str string = {0};
PHP_VAR_SERIALIZE_INIT(value_hash);
php_var_serialize(&string, &pZval, &value_hash TSRMLS_CC);
PHP_VAR_SERIALIZE_DESTROY(value_hash);
if (string.c == NULL || string.len == 0)
return NULL;
Z_STRLEN_P(_res) = string.len;
Z_STRVAL_P(_res) = (char *)cache_malloc(Z_STRLEN_P(_res));
cache_cpy(Z_STRVAL_P(_res),string.c,Z_STRLEN_P(_res));
}
break;
...
}
PHP:
static inline zval* cache_zval_unserialize(zval *pZval TSRMLS_DC)
{
default:
{
php_unserialize_data_t var_hash;
PHP_VAR_UNSERIALIZE_INIT(var_hash);
if (!php_var_unserialize(&_res, &Z_STRVAL_P(pZval), Z_STRVAL_P(pZval) + Z_STRLEN_P(pZval), &var_hash TSRMLS_CC))
{
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
efree(_res);
}
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
}
break;
}
PHP:
static inline zval* cache_zval_unserialize(zval *pZval TSRMLS_DC)
{
default:
{
php_unserialize_data_t var_hash;
char* unserialize_begin = Z_STRVAL_P(pZval);
char* unserialize_end = unserialize_begin + Z_STRLEN_P(pZval);
PHP_VAR_UNSERIALIZE_INIT(var_hash);
if (!php_var_unserialize(&_res, &unserialize_begin, unserialize_end, &var_hash TSRMLS_CC))
{
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
efree(_res);
}
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
}
break;
}

Вот и все!
Спасибо всем кто читал, может хоть кому-то пригодится.
PS: данный экстеншен можно брать только за основу, не более, он оставляет большое поле для деятельности (синхронизация и прочее).
Исходники, и откомпилированная dll для Windows (php 5.2.4) в комплекте (см. вверху).