Пишемо розширення з допомогою бібліотеки php-cpp для php7

Кожен php програміст, хоча б раз у житті замислювався про написання свого розширення для php. Сьогодні я хочу розповісти про те як написати розширення з допомогою бібліотеки PHP-CPP. На прикладі виведення алерта з кнопкою, за допомогою gtk.


Тим кому цікавий процес прошу під кат.

Залежності:
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 <phpcpp.h>
#include < iostream>
#include <gtk/gtk.h>

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 function C
*/
extern "С" {
/**
* Function that is called by PHP right after the process PHP
* 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"); // реєструємо клас
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 <phpcpp.h>
#include < iostream>
#include <gtk/gtk.h>

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 function C
*/
extern "С" {
/**
* Function that is called by PHP right after the process PHP
* 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");
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:
<?php
function alert($title) {
$gtk = new Gtk;
return $gtk
->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++ це один з перших дослідів, по цьому, якщо що не так з кодом, пишіть в коментарі, і хороших вихідних.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.