Создание GUI приложений на PHP

16ffdc8e6e354ca5bc870fba73e5060b.png
Темой о разработке GUI приложений на PHP сегодня, пожалуй, уже никого не удивишь. Для этого существует не одно решение, есть как развивающиеся проекты, так и умершие. Но этот пост будет не о тех и не о других, а о новом расширении для PHP — библиотеке PHPQt5, а точнее о её более продвинутой реинкарнации — о PQEngine.
P.S. PHPQt5 не имеет ничего общего с более известной библиотекой php-qt!

Предисловие


По сути, PQEngine — это SAPI модуль, но позиционируется он как движок, реализующий интерфейс для исполнения php-скриптов при помощи Zend API и предоставляющий доступ к части фреймворка Qt.
Пока, к сожалению, к очень малой его части. Можно выделить основные из них: визуальные компоненты Qt (Widgets), компоновщики (Layouts), система соединений сигнал->слот (connect()), управление событиями (QEvents) и многопоточность, предоставляемая классом QThread.

Создание проекта


Для упрощения создания и сборки проектов существует очень простая в использовании утилита — PQBuilder. Это приложение на 99% написано на PHP и демонстрирует скромные возможности движка PQEngine. Скачать его можно на официальном сайте библиотеки PHPQt5: http://phpqt.ru/download/pqbuilder

Интерфейс сборщика проектов:
06cbce46bf714c49af986464a42f53dd.png

Все что от нас требуется — это указать название проекта, путь его размещения и шаблон. В зависимости от выбранного шаблона, сборщик создаст php-файл с базовым кодом, которого будет достаточно для запуска будущего приложения, затем откроет проводник Explorer в папке проекта, где мы увидим всего два файла и одну папку:

  • main.php — основной файл проекта с исходным кодом приложения;
  • %projectname%.pqb — файл проекта PQBuilder, в котором храниться некоторая информация о созданном проекте;
  • build — каталог сборки проекта.


Созданный проект с шаблоном QWidget Application
66c39952ca934e959684adf67de3d981.png

Код который мы видим на скриншоте был скопирован из шаблона и при выполнении покажет пустую форму.

Примечание 1
На самом деле для запуска формы совсем необязательно наследоваться от QWidget, предлагаемый код — это лишь пример. Единственный обязательный участок кода тут — qApp::exec();, где qApp — это ссылка на экземпляр класса QApplication, а функция exec() обеспечивает переход в главный цикл обработки событий Qt, тем самым предотвращая завершение работы PHP до тех пор, пока не будет вызвана функция exit() или quit(), либо пока не будут закрыты все видимые формы приложения.

Самый элементарный способ показать форму Qt выглядит примерно так:

<?php
$widget = new QWidget;
$widget->show();

qApp::exec();



Помимо того, что Сборщик переместил нас в директорию проекта, он активировал вкладку сборки, где нам остаётся лишь выбрать иконку для будущего приложения и шаблон сборки.

Примечание 2
На момент написания этой статьи существовало 3 шаблона сборки проектов. Вкратце о каждом:
  • Simple (app)
    — сборка простого приложения, без использования шифрования и упаковки. Основной PHP-файл проекта копируется в рабочую директорию приложения, при этом исходный код PHP-файла остаётся открытым.
  • Simple + manifest (app-manifest)
    — сборка простого приложения с подключением файла Manifest. О файле Manifest, для чего он нужен и как его оформлять, можно почитать на сайте Microsoft.
  • Packed (packedapp)
    — самый интересный метод сборки приложения, при котором основной PHP-файл зашифровывается и упаковывается в ресурсы приложения путём компиляции. это позволяет защитить исходный код программы. Большой минус этого шаблона в том, что он позволяет упаковывать только файл main.php, другие ресурсы (картинки, текст и п.р.) остаются в папке с проектом.


Для своего проекта я выбрал шаблон Simple — он очень удобен на этапе разработки в плане того, что для дебага проекта не придётся каждый раз пересобирать приложение.
Нажав на кнопку сборки проекта, PQBuilder запустит компилятор, работа которого продлится от 10 до 30 секунд, затем откроется директория собранного проекта и, если процесс компиляции прошел успешно, мы увидим свежеиспеченный исполняемый файл.

Примечание 3

Если же exe-файл не появился — это означает, что во время компиляции произошли ошибки. Сборщик не предоставляет информации об ошибках компиляции, но она доступна в log-файле, который лежит в директории на уровень выше: make.log. Как правило, сообщение об ошибке можно найти в конце файла. Возможно изучение этого лога поможет вам определить ошибку и устранить её.


Для релиза приложения нам нужны только 4 файла: exe-файл, pqengine.dll, php5ts.dll и main.php. Остальные файлы в директории проекта — это временные файлы созданные компилятором, их можно смело удалить.

Разработка приложения


Примечание 4
Благодаря тому, что все зарегистрированные в движке PQEngine классы реализуют интерфейсы стандартных классов Qt, движок, теоретически, способен проглотить часть примеров с официальной документации Qt, достаточно лишь убрать типы переменных и подписать к ним знак $. Но тем не менее, некоторые функции остаются недоступными, либо отличаются набором и типами входных и выходных значений.

Посмотреть полный список методов того или иного класса можно в заголовочных файлах PQEngine, найти их можно в директории с установленным сборщиком: %путь_установки%\PQBuilder\pqenginedll\pqclasses
Все методы начинающиеся с макроса Q_INVOKABLE доступны для вызова из PHP-кода, а выполняют они ровно то, что написано в официальной документации Qt.


Разработку приложения мы продолжим с предложенным шаблонным кодом, непосредственно в директории с собранным проектом. Открываем файл build\app\release\main.php в любимом редакторе, переходим в тело функции initComponents() и приступаем:

  1. Для начала создадим и установим компоновщик для нашей формы.
    Примечание 5
    PQEngine предоставляет три вида компоновки:
    1. QVBoxLayout — вертикальное расположение виджетов;
    2. QHBoxLayout — горизонтальное расположение виджетов;
    3. QGridLayout — расположение виджетов в сетке.

    private function initComponents() {
        $layout = new QGridLayout;
        $this->setLayout($layout);
    }
    
    

  2. Затем добавим несколько кнопок:
    private function initComponents() {
        $layout = new QGridLayout;
        $this->setLayout($layout);
        
        $button1 = new QPushButton($this); // $this - это родитель кнопки. В качестве родителя устанавливаем нашу форму
        
        /* Устанавливаем текст кнопки. Это можно сделать как через свойство text, 
         * так и с помощью метода setText(); 
         * см. Примечание 4
         */
        $button1->text = 'Показать сообщение';
    
        /* Объекты поддерживают анонимные функции для установки событий.
         * На вход анонимной функции всегда передается 2 параметра: ссылка на
         * объект вызвавший функцию и ссылка на объект события (QEvent)
         */
        $button1->onClicked = function($sender, $action) {
            QMessageBox::information($this,
                                      'Первое сообщение',
                                      'Привет, мир!');
        };
        
        $button2 = new QPushButton($this);
        $button2->text = 'О PQEngine';
        $button2->onClicked = function($sender, $action) {
            aboutPQ(); // это функция предоставляется движком PQEngine
        };
        
        $button3 = new QPushButton($this);
        $button3->text = 'Выход';
        /* Для разнообразия покажу пример использования функции connect().
         * Всё очень просто: вы указываете какой сигнал виджета с каким слотом или функцией 
         * необходимо соединить. Я выбрал соединение с функцией php
         * Подробнее можно почитать в документации к PHPQt5:
         * http://phpqt.ru/documentation/functions/connect
         */
        $button3->connect(SIGNAL('clicked(bool)'), $this, SLOT('quitButtonClicked(bool)'));
    }
    
    // Функция-слот обязательно должна быть объявлена как публичный метод 
    public function quitButtonClicked($sender, $checked) {
        $this->close();
    }
    
    

  3. И, наконец, добавим все эти кнопки в созданный компоновщик:
        /* ... */
        $button3->connect(SIGNAL('clicked(bool)'), $this, SLOT('quitButtonClicked(bool)'));
    
        /* Функция addWidget принимает 3 или 5 параметров:
         * addWidget($widget, $row, $column, $rowSpan = 1, $columnSpan = 1), где:
         * $widget - ссылка на добавляемы виджет;
         * $row - номер строки размещения виджета;
         * $column - номер столбца размещения виджета;
         * $rowSpan - количество строк, объединяемых виджетом;
         * $columnSpan - количество столбцов, объединяемых виджетом;
         */
        $layout->addWidget($button1, 0, 0);
        $layout->addWidget($button2, 0, 1);
        $layout->addWidget($button3, 1, 0, 1, 2);
        
        /* Для того чтобы при растягивании формы наши кнопки оставались прижатыми 
         * к верху окна, можно добавить виджет-пустышку в качестве распорки
         */
        $layout->addWidget(new QWidget, 2, 0);  
    
    


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

Будьте внимательны!

Выбранный в данном примере шаблон (Simple) позволяет работать именно таким образом, ведь по сути сборка приложения нужна лишь для того, чтобы прикрепить к приложению иконку, выбранную пользователем во вкладке «Сборка проекта».
НО! Будьте внимательны и осторожны! Не забывайте, что все изменения мы производили в директории с собранным проектом, поэтому если в PQBuilder'e вы снова соберёте этот проект, весь код будет затёрт!


P.S.

Такой фокус не прошел бы, если бы мы собирали приложение с шаблоном "Packed", ведь там исходный PHP-код прикрепляется в ресурсы исполняемого файла, поэтому при каждом изменении исходника приходилось бы заново пересобирать проект.

И вот что должно было получиться:
a1fbce49064f412799f88fc55965b27a.png

Архив проекта (распаковать и открыть файл untitled.pqb)

© Habrahabr.ru