Разработка расширений php: Классы, интерфейсы, методы

Наступили выходные, прошел IT.conf_2012

И снова я тут, и снова мы будем изучать недры PHP, и сегодня мы поговорим о классах, интерфейсах, методах классов, финализированных классах и прочих няшках.

Приступим

Для начала мы объявим список методов для класса Hello, это будут методы __construct () и say ():

PHP_METHOD(Hello, __construct);
PHP_METHOD(Hello, say);

static const zend_function_entry hello_methods[] = {
    PHP_ME(Hello, __construct, NULL, ZEND_ACC_PUBLIC)
    PHP_ME(Hello, say, NULL, ZEND_ACC_PUBLIC)
    PHP_FE_END
};

Тут мы уже вместо макросов PHP_FUNCTION () и PHP_FE () используем PHP_METHOD () и PHP_ME () в таблице функций. Макрос PHP_METHOD () принимает два аргумента: первый это имя класса, а второй — собсвенно имя метода. PHP_ME () принимает те же аргументы, но он имеет третий аргумент — флаги:

  • ZEND_ACC_PUBLIC — публичный метод
  • ZEND_ACC_PRIVATE — приватный метод
  • ZEND_ACC_PROTECTED — protected‐метод
  • -------
  • ZEND_ACC_STATIC — статический метод
  • ZEND_ACC_ABSTRACT — абстрактный метод
  • ZEND_ACC_FINAL — финализированный метод

С этим вроде бы все понятно… Перейдем к объявлению классов.

Объявление

Объявление классов происходит в MINIT‐функции (MINIT, MSHUTDOWN — методы вызывающиеся при загрузке модуля, и его выгрузке, о них поговорим потом), и объявление будет примерно таким:

static PHP_MINIT_FUNCTION(hello_world) {
    zend_class_entry ce;
    zend_class_entry ie;
    INIT_CLASS_ENTRY(ce, "Hello", hello_methods);
    INIT_CLASS_ENTRY(ie, "IWorld", NULL);
    zend_register_internal_interface(&ie);
    zend_class_implements(&ce, 1, ie);
    zend_class_entry *rce = zend_register_internal_class(&ce);
    rce->ce_flags |= ZEND_ACC_FINAL_CLASS;
}

В первых двух строчках мы объявляем две точки входа в класс PHP, ce — для класса, ie — для интерфейса. В следующих двух строчках — мы инициализируем класс Hello с таблицей методов hello_methods, и интерфейс IWorld (да‐да, интерфейсы инициализируются как классы, это какая то хрень, имхо). Далее — мы регистрируем внутренний интерфейс с помощью функции zend_register_internal_interface () которая в качестве аргумента принимает ссылку на точку входа в класс (интерфейс) — &ie. После этого с помощью метода zend_class_implements () мы указываем что &ce (Hello) должен реализовывать интерфейс &ie (IWorld). Данный метод имеет >3 аргументов: первый — ссылка на класс который должен реализовывать методы, второй — количество интерфейсов которые будут реализованы, и остальные аргументы — сами интерфейсы. После того как мы указали что Hello реализует IWorld — можно зарегистрировать сам класс с помощью метода zend_register_internal_class () который в качестве аргумента принимает ссылку на точку входа в класс, и возвращает указатель на нее, с которым дальше необходимо продолжать работать.

После всех этих действий мы можем модифицировать флаги класса, к примеру — сделать класс финализированным. Для этого мы будем использовать указатель который вернула нам функция zend_register_internal_class (), поле структуры _zend_class_entry под названием ce_flags, и собственно сам флаг — ZEND_ACC_FINAL_CLASS.

Изначально у меня не получилось навешать реализацию интерфейса на класс после регистрации самого класса, но чуть подумав немного переписал MINIT‐функцию, и… Можно так:

static PHP_MINIT_FUNCTION(hello_world) {
    zend_class_entry ce;
    zend_class_entry ie;
    INIT_CLASS_ENTRY(ce, "Hello", hello_methods);
    INIT_CLASS_ENTRY(ie, "IWorld", NULL);
    zend_class_entry *rie = zend_register_internal_interface(&ie);
    zend_class_entry *rce = zend_register_internal_class(&ce);
    zend_class_implements(&*rce, 1, rie);
    rce->ce_flags |= ZEND_ACC_FINAL_CLASS;
}

Кардинально ничего не изменилось, но все же…

После этого мы изменим точку входа в модуль добавив в него MINIT‐функцию:

zend_module_entry hello_world_module_entry = {
    STANDARD_MODULE_HEADER,
    "hello_world",
    my_functions,
    PHP_MINIT(hello_world), // name of the MINIT function or NULL if not applicable
    NULL, // name of the MSHUTDOWN function or NULL if not applicable
    NULL, // name of the RINIT function or NULL if not applicable
    NULL, // name of the RSHUTDOWN function or NULL if not applicable
    NULL, // name of the MINFO function or NULL if not applicable
    NO_VERSION_YET,
    STANDARD_MODULE_PROPERTIES
};

И сделаем простую реализацию методов __construct () и say ():

PHP_METHOD(Hello, __construct) {
    /* nothing */
}

PHP_METHOD(Hello, say) {
    php_printf("Hello world
");
}

После этого собираем модуль и устанавливаем:

make && sudo make install

И проверяем:

popsul@xx:~/projects/php_ext$ php -r "$i = new Hello(); var_dump($i instanceof IWorld);"
bool(true)
popsul@xx:~/projects/php_ext$ php -r "class X extends Hello {}; $x = new X;"
PHP Fatal error:  Class X may not inherit from final class (Hello) in Command line code on line 1
popsul@xx:~/projects/php_ext$ php -r "$i = new IWorld();"
PHP Fatal error:  Cannot instantiate interface IWorld in Command line code on line 1
PHP Stack trace:
PHP   1. {main}() Command line code:0

В конечном итоге у нас появился класс Hello, инстанс которого является instanceof IWorld, так же класс Hello является финальным и его невозможно унаследовать, а так же интерфейс у нас действительно интерфейс, и создать его инстанс невозможно.

И конечно же метод say ():

popsul@xx:~/projects/php_ext$ php -r "$i = new Hello(); $i->say();"
Hello world

Заключение

Как видим, все работает и ничего сложного :)

Исходник всего этого можно взять тут -> hello_world.c

«Добра вам! Счастья! Здоровья! » © какой‐то клоун.

comments powered by Disqus