[recovery mode] Пишем расширение с помощью библиотеки php-cpp для php7

Каждый php программист, хотя бы раз в жизни, задумывался о написания своего расширения для php. Сегодня я хочу рассказать о том как написать расширение с помощью библиотеки PHP-CPP. На примере вывода алерта с кнопкой, с помощью gtk.
337d03c3296743c7b01a0bf13644c35d.png
d9d95517e1db4b028bda07f7a1e9e543.png
Тем кому интересен процесс прошу под кат.

Зависимости:
1. libgtk2.0-dev
2. php7.0-dev
3. php-cpp — библиотека которая нам поможет все разработать
Первые две зависимости ставятся из репозиториев вашей linux дистрибутива.

$ apt-get install libgtk2.0-dev php7.0-dev

Для установки php-cpp клонируем репозиторий с github.com php-cpp, я всё буду собирать в tmp директории
$ cd /tmp/
$ git clone https://github.com/CopernicaMarketingSoftware/PHP-CPP.git
$ cd PHP-CPP/ # переходим в директорию с исходниками
$ make # этой командой собираем библиотеку
$ sudo make install # и устанавливаем в систему


И так всё готово, теперь мы можем приступить к разработки, создаём директорию с проектом у меня она называется gtkPHP7. Прежде чем начать, скачаем скелетон будущего расширения по ссылке empty-extension.zip, и распаковываем архив в папку проекта. После чего, у нас появятся следующие файлы:
1. extensions.ini
2. Makefile
3. main.cpp
Переименуем extensions.ini и откроем его на редактирование. Заменим строку 'extensions=extensions.ini' на 'extensions=gtkphp7.ini'.
Далее, нам необходимо внести изменения в файл Makefile, Отредактировав в нём следующие строки

NAME                            = gtkphp7
INI_DIR                         =       /etc/php/7.0/mods-available/
COMPILER_LIBS           =       `pkg-config --cflags --libs gtk+-2.0`

all:                                    ${OBJECTS} ${EXTENSION} 

${EXTENSION}:                   ${OBJECTS}
                                                ${LINKER} ${LINKER_FLAGS} -o $@ ${OBJECTS} ${LINKER_DEPENDENCIES} ${COMPILER_LIBS}

NAME — имя нашего extensions
INI_DIR — директории для конфигураций модулей php, у меня получилась /etc/php/7.0/mods-available/ ваше же может отличаться.
COMPILER_LIBS — подгружает необходимые нам библиотеки в данном случае gtk
И последним я добавил COMPILER_LIBS в цель all.

Перейдём, непосредственно, к разработки расширения. Для этого я установил clion от jetbrains и импортировал проект в ide. Все остальные работы мы будем проводить в файле main.cpp. Я решил разработать класс который будет инстанцироваться в php, и с последовательными методами формировать окошко alert’а.

#include 
#include 
#include 

class Gtk : public Php::Base {
private:
    GtkWidget *_window;
    char *_titleWindow;
    char *_buttonTitle;
    GtkWidget *_button;
    Php::Value callB;

public:
    Php::Value setTitle(Php::Parameters ¶ms);
    Php::Value setButtonTitle(Php::Parameters ¶ms);
    Php::Value createWindow();
    Php::Value setButton();
    static void callback(GtkButton *button, gpointer data);
    Php::Value render();
};

Это прототип класса расширения:
1. setTitle — устанавливает заголовок окна
2. setButtonTitle — заголовок кнопки
3. createWindow — создаёт окно
4. setButton — создаёт кнопку и устанавливает в окне
5. callback — кэлбэк функция которая вызывается при клике на кнопку
6. render — выводит окно с кнопкой на экран
Первые два метода опустим, так как они просто устанавливают соответствующие переменные для использования в качестве заголовков.
/**
 * create window gtk
 * @return Gtk
 */
Php::Value Gtk::createWindow() {
    int argc = 0;
    char **argv = NULL;
    gtk_init(&argc, &argv);
    _window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(_window), _titleWindow);
    gtk_container_set_border_width(GTK_CONTAINER(_window), 50);
    return this;
}

Для того что бы инициализировать gtk, мы создали переменные argc и argv соответственно передав им 0 и NULL, как будто бы, приложение было запущено без параметров. Окно мы создаём методом 'gtk_window_new' и присваиваем переменной _window — это приватный параметр класса.
/**
 * set button gtk
 * @return Gtk
 */
Php::Value Gtk::setButton() {
    _button = gtk_button_new_with_label(_buttonTitle);
    gtk_container_add(GTK_CONTAINER(_window), _button);
    g_signal_connect(G_OBJECT(_button), "clicked", G_CALLBACK(&Gtk::callback), G_OBJECT(_window));
    return this;
}

Этот метод создаёт виджет кнопки и присваивает её окну _window. Функция g_signal_connect отслеживает нажатие кнопки мы передаём в функцию следующие параметры:
1. объект кнопки
2. тип действия
3. callback функция (она не должна быть членом класса, другими словами она должна быть либо статичной либо отдельной функцией)
4. здесь мы передаём объект окна для использования в callback функции.
Разберём callback-функцию:
/**
 * callback click
 * @param button
 * @param window
 */
void Gtk::callback(GtkButton *button, gpointer window) {
    gtk_widget_destroy(GTK_WIDGET(window));
    gtk_main_quit();
}

Второй параметр это как рас, то что было передано 4 параметром в функции выше, первый собственно наша кнопка. Функция проста она просто выходит из gtk.
Ну и последнем рассмотрим функцию render:
/**
 * render alert
 * @return
 */
Php::Value Gtk::render() {
    gtk_widget_show_all(_window);
    gtk_main();
    return true;
}

В общем метод показывает все виджеты окна, и запускает gtk. После остановки, посредством callback функции она вернёт true.
Последним шагом, мы должны зарегистрировать наш класс и его методы:
/**
 *  tell the compiler that the get_module is a pure C function
 */
extern "C" {
/**
 *  Function that is called by PHP right after the PHP process
 *  has started, and that returns an address of an internal PHP
 *  strucure with all the details and features of your extension
 *
 *  @return void*   a pointer to an address that is understood by PHP
 */
PHPCPP_EXPORT void *get_module() {
    static Php::Extension extension("gtkphp7", "1.0");
    Php::Class gtk("Gtk"); // регистрируем класс
    gtk.method<&Gtk::setTitle>("setTitle"); // так регистрируем методы класса
    gtk.method<&Gtk::setButtonTitle>("setButtonTittle");
    gtk.method<&Gtk::setButton>("setButton");
    gtk.method<&Gtk::createWindow>("createWindow");
    gtk.method<&Gtk::render>("render");
    extension.add(std::move(gtk)); // регистрируем класс и его метод как нативные 
    // return the extension
    return extension;
}
}

Полный код main.cpp под спойлером:
main.cpp
#include 
#include 
#include 

class Gtk : public Php::Base {
private:
    GtkWidget *_window;
    char *_titleWindow;
    char *_buttonTitle;
    GtkWidget *_button;
    Php::Value callB;

public:
    Php::Value setTitle(Php::Parameters ¶ms);
    Php::Value setButtonTitle(Php::Parameters ¶ms);
    Php::Value createWindow();
    Php::Value setButton();
    static void callback(GtkButton *button, gpointer data);
    Php::Value render();
};

/**
 * set title to window
 * @param params
 * @return Gtk
 */
Php::Value Gtk::setTitle(Php::Parameters ¶ms) {
    std::string title = params[0];
    _titleWindow = new char[title.size() + 1];
    std::copy(title.begin(), title.end(), _titleWindow);
    _titleWindow[title.size()] = '\0';
    return this;
}

/**
 * set button title
 * @param params
 * @return Gtk
 */
Php::Value Gtk::setButtonTitle(Php::Parameters ¶ms) {
    std::string title = params[0];
    _buttonTitle = new char[title.size() + 1];
    std::copy(title.begin(), title.end(), _buttonTitle);
    _buttonTitle[title.size()] = '\0';
    return this;
}

/**
 * create window gtk
 * @return Gtk
 */
Php::Value Gtk::createWindow() {
    int argc = 0;
    char **argv = NULL;
    gtk_init(&argc, &argv);
    _window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(_window), _titleWindow);
    gtk_container_set_border_width(GTK_CONTAINER(_window), 50);
    return this;
}

/**
 * set button gtk
 * @return Gtk
 */
Php::Value Gtk::setButton() {
    _button = gtk_button_new_with_label(_buttonTitle);
    gtk_container_add(GTK_CONTAINER(_window), _button);
    std::cout << callB << std::endl;
    g_signal_connect(G_OBJECT(_button), "clicked", G_CALLBACK(&Gtk::callback), G_OBJECT(_window));
    return this;
}

/**
 * callback click
 * @param button
 * @param window
 */
void Gtk::callback(GtkButton *button, gpointer window) {
    gtk_widget_destroy(GTK_WIDGET(window));
    gtk_main_quit();
}

/**
 * render alert
 * @return
 */
Php::Value Gtk::render() {
    gtk_widget_show_all(_window);
    gtk_main();
    return true;
}

/**
 *  tell the compiler that the get_module is a pure C function
 */
extern "C" {
/**
 *  Function that is called by PHP right after the PHP process
 *  has started, and that returns an address of an internal PHP
 *  strucure with all the details and features of your extension
 *
 *  @return void*   a pointer to an address that is understood by PHP
 */
PHPCPP_EXPORT void *get_module() {
    static Php::Extension extension("gtkphp7", "1.0");
    Php::Class gtk("Gtk");
    gtk.method<&Gtk::setTitle>("setTitle");
    gtk.method<&Gtk::setButtonTitle>("setButtonTittle");
    gtk.method<&Gtk::setButton>("setButton");
    gtk.method<&Gtk::createWindow>("createWindow");
    gtk.method<&Gtk::render>("render");
    extension.add(std::move(gtk));
    // return the extension
    return extension;
}
}


Переходим к следующему шагу, компиляции и установки расширения:
$ make
$ sudo make install

После установки делаем симлинки для подключения или phpenmod
$ sudo ln -s /etc/php/7.0/mods-available/gtkphp7.ini /etc/php/7.0/cli/conf.d/20-gtkphp7.ini
$ sudo service php7.0-fpm restart # перезапускаем php
$ php -m | grep gtkphp7 # проверяем есть ли расширения

Для пересборки и переустановки расширения достаточно выполнить
$ make clean
$ make
$ sudo make install

Для теста был написан простой скрипт на php:
setTitle($title)
    ->setButtonTittle("Ok")
    ->createWindow()
    ->setButton()
    ->render();
}

if(alert("Hellow habr")) {
   alert("Hellow again");
}

Результат скрипта, те самые скриншоты в заголовке статьи…
Для удобства работы над расширением была написана функция
void dump(Php::Value dumping) {
   Php::call("var_dump",dumping);
}

Она вызывает php функцию var_dump, довольно удобно делать дампы переменных массивов php и т.д.

В качестве выводов приведу несколько ссылок:
1. Документация по php-cpp
2. Репозиторий с примером из статьи
p.s. На c++ это один из первых опытов, по этому, если что не так с кодом, пишите в комментарии, и хороших выходных.

Комментарии (1)

  • 2 октября 2016 в 08:47

    0

    У gtk есть менее многословные биндинги. Оно конечно занимательно, но вот реальных desktop-проектов на php я не встречал. Есть какие-нибудь интересные примеры? Гугление showcases выдает какие-то калькуляторы…

© Habrahabr.ru