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

И снова я тут…

Продолжаем разбираться с интерфейсами и методами, и сегодня мы поговорим о реализации интерфейсных методов в классах. Сегодня мы познакомимся с тем, как реализовать на уровне PHP‐расширения код аналогичный этому:

interface IWorld {
    public function say();
}
class Hello implements IWorld {
    public function say() {
        printf("Hello world");
    }
}

Чтож, приступим…

За основу возьмем предыдущий исходник и модифицируем его.

Для начала объявим точки входа в интерфейс и класс, метод‐реализацию Hello: say, и таблицы методов:

//точки входа в интерфейс и класс
static zend_class_entry *iworldEntry;
static zend_class_entry *helloEntry;

//декларация метода Hello::say
PHP_METHOD(Hello, say);

//таблица функций для интерфейса IWorld
static const zend_function_entry iworld_methods[] = {
    PHP_ABSTRACT_ME(IWorld, say, NULL)
    PHP_FE_END
};

//таблица функций для класса Hello
static const zend_function_entry hello_methods[] = {
    PHP_ME(Hello, say, NULL, ZEND_ACC_PUBLIC)
    PHP_FE_END
};

Как уже говорилось ранее — интерфейсы в понимании пшп — это просто классы, и из этого вытекает что и методы в интерфейсах такие же как и в классах, то‐есть — абстрактные. Макрос PHP_ABSTRACT_ME отличается от PHP_ME только тем, что в нем не описываются доступность метода (public, private, protected) — по умолчанию оно объявляет метод с флагами ZEND_ACC_PUBLIC | ZEND_ACC_ABSTRACT, но если сильно хочется — можно дергать макрос ZEND_FENTRY напрямую -, но вот только как оно работает — хз, ибо в в него не передается имя класса для которого объявлен метод. Чудесный PHP с чудесной документацией которой нет :)

Далее переходим к модификации MINIT‐функции. В конечном итоге она получилась примерно такой:

static PHP_MINIT_FUNCTION(hello_world) {
    //инициализируем интерефейс IWorld с таблицей методов iworld_methods
    zend_class_entry ie;
    INIT_CLASS_ENTRY(ie, "IWorld", iworld_methods);
    iworldEntry = zend_register_internal_interface(&ie);

    //инициализируем класс Hello с таблицей методов hello_methods
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "Hello", hello_methods);
    //делаем класс не абстрактным
    ce.ce_flags ^= ZEND_ACC_ABSTRACT;
    helloEntry = zend_register_internal_class(&ce);
    //реализуем интерфейс IWorld
    zend_class_implements(&*helloEntry, 1, iworldEntry);
}

Как видим, от предыдущего примера отличий кардинальных нету за исключением того, что при инициализации интерфейса мы указываем таблицу методов, и то, что внезапным образом класс который реализует интерфейс становится абстрактным по непонятным мне причинам.

Я изначально попытался не реализовывать метод Hello: say, собрал расширение, запустил, и увидел SEGFAULT… Так что можно сделать вывод: в расширениях PHP нельзя не реализовывать методы, не будет тут fatal error, тут будет SEGFAULT. Но это наверное очевидно все‐таки.

В общем то, все готово, остается написать только саму реализацию метода Hello: say:

PHP_METHOD(Hello, say) {
    printf("Hello World
");
}

Собрать и запустить:

popsul@xx:~/projects/php_ext$ php -r '$a = new Hello(); $a->say(); var_dump($a instanceof IWorld);'
Hello World
bool(true)
popsul@xx:~/projects/php_ext$ php -r 'class A implements IWorld {}'
PHP Fatal error:  Class A contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (IWorld::say) in Command line code on line 1
PHP Stack trace:
PHP   1. {main}() Command line code:0

Задача выполнена!

Вместо заключения

Исходник можно взять тут, и если вас еще интересует эта серия статей — возвращайтесь через пару дней, напишу еще чего‐нибудь :)

comments powered by Disqus