2. Зачем понадобилось расширение?
Имеется собственный движок для разработки сайтов
и систем автоматизации бизнеса (PHP+MySQL+JS).
Движок используется с 2006 года.
Используется собственный шаблонизатор,
к которому мы очень привыкли.
Вопрос «Почему не использовали [SMARTY|XSLT|…]» оставим за рамками доклада
3. Как выглядит шаблонизатор?
Пример шаблона: Результат:
<ul> <ul>
{:list} <li> первый пункт </li>
<li> {!item} </li> <li> второй пункт </li>
{:/list} </ul>
</ul>
Подача данных в шаблон:
Array( “list” => Array (
Array ( “item” => ”первый пункт” ),
Array ( “item” => ”второй пункт” )));
Вопрос о достоинствах и недостатках шаблонизатора оставим за рамками
доклада
4. Как это обрабатывалось в PHP?
Примерно такой конструкцией:
$result = preg_replace(”/
{([?~:])([a-zA-Z0-9_]*)(([<>!=]{1,2})
([a-zA-Z0-9_]*)){0,1}}(.*){1/2}
/iseU”,
”ProcessBlock(‘$0′, ‘$1′, ‘$2′, ‘$4′, ‘$5′, ‘$6′, $vars_arr) ”,
$result);
Регулярное выражение с eval’ом
5. И что, это работало?
Да!
За пять лет на этом движке было создано
порядка двухсот проектов.
И все было бы хорошо, если бы на некоторых проектах
нас не стала подводить производительность парсера.
В определенных условиях он потребляет серьезное
количество ресурсов – памяти и процессора.
Что же нам делать???
6. Что же нам делать?
Первой идеей было создание компилирующего
шаблонизатора. Шаблон должен трансформироваться
в HTML, перемешанный с простыми управляющими
конструкциями PHP (циклы, условия, вывод).
На вход такому скрипту-шаблону подаются данные,
скрипт выполняется, получаем HTML-страницу.
Не получилось – шаблонизатор является управляющим
(возможен вызов программных модулей и их методов
из шаблона), шаблоны могут каскадироваться.
Скорее всего, задача решаема – но из наших программистов не решил ее никто.
7. Какие проблемы были
с компилирующим шаблонизатором?
Схема парсинга:
Шаблон
Шаблон Шаблон
программного
страницы интерфейса
модуля
Тело Класс в PHP Метод в классе в PHP
страницы
Скорее всего, задача решаема – но из наших программистов не решил ее никто.
8. А напишем расширение!
Вторая идея – написать на C++ расширение, которое будет
сливать шаблон с данными.
И это сработало!
На пути нам встретились три основные проблемы.
На самом деле, их было значительно больше.
9. Первая проблема: как в недрах PHP представлены
ассоциативные массивы…
Данные, хранящиеся в ассоциативных массивах в PHP,
становятся в C++ объектами HashTable.
Работать с ними можно при помощи набора функций –
довольно неудобных (начать с названий – как вам нравится
zend_hash_internal_pointer_reset_ex?).
Работа с многоэтажными массивами превращается
в сложное занятие.
А у нас в движке данные как раз хранятся в многоэтажных
массивах. Каждый уровень вложенности (метод, цикл) –
это новый уровень вложенности массива.
В принципе, можно нас обвинить в том, что мы сами себе придумали проблему.
10. Как решили?
За один проход разворачиваем всю иерархию
ассоциативных массивов в два обычных массива –
один с ключами, второй со значениями.
Ключи при этом собираем в одну строку.
$arr = Array ( “first” => 1,
“second” => Array (
Array ( “number” => 2 )));
Keys = [ "first ", "second0number" ];
Values = [ "1", "2" ];
Заодно преобразуем и ключи, и значения в строки.
Поиск значения с любого «этажа» - чистое удовольствие!
11. Вторая проблема: как вызвать функцию,
содержащуюся в PHP-скрипте, из расширения?
zval *function, *itemname, *retval;
zval **params[1]; пусть itemname – параметр функции function
MAKE_STD_ZVAL ( function );
MAKE_STD_ZVAL ( itemname );
ZVAL_STRING ( function, “MyPHPFunction“, 1);
ZVAL_STRING ( itemname, “значение“, 1);
нигде не определенный параметр
params[0] = &itemname;
if ( call_user_function_ex ( CG (function_table), NULL, function, &retval, 1, params, 0,
NULL TSRMLS_CC ) == FAILURE ) непонятный макрос
zend_error (E_ERROR, “Function call failed“);
else {
if ( Z_TYPE_P (retval) == IS_STRING ) {
… тут мы можем обработать возвращенное значение, используя
макросы
Z_STRLEN_P ( retval ) и Z_STRVAL_P ( retval )
Этот код работает только внутри функции, вызываемой из PHP:
ZEND_FUNCTION(Func)
12. Как решили?
Документация тут не поможет… лезем в исходники PHP.
Оказывается, function_table – это параметр, передаваемый
в ZEND_FUNCTION.
За макросом TSRMLS_CC скрывается переменная tsrm_ls,
тоже являющаяся одним из стандартных параметров для
функций, объявленных при помощи ZEND_FUNCTION.
Остается передать эти параметры в нашу innerCPPFunction:
char *result = innerCPPFunction ( CG ( function_table ), tsrm_ls );
char *innerCPPFunction ( HashTable *function_table, void ***tsrm_ls )
{…}
innerCPPFunction – это функция, куда мы захотели вынести вызов функции PHP
13. Передача выполнения между PHP и расширением
Схема парсинга:
Шаблон
Шаблон Шаблон
программного
страницы интерфейса
модуля
Тело Класс в PHP Метод в классе в PHP
страницы
PHP => extension => PHP => extenstion => PHP => extension
Скорее всего, задача решаема – но из наших программистов не решил ее никто.
14. Третья проблема: глобальные переменные
Глобальные переменные (например, общие массивы ключей
и значений), объявленные в расширении, сохраняют свои
значения между вызовами различных функций из PHP.
Приходится о них заботиться. Учитывая, что схема парсинга
выглядит так: сначала из PHP вызывается функция
расширения, которая сливает данные с шаблоном верхнего
уровня, а затем она вызывает функции из PHP, которые
инициализируют содержащиеся в странице программные
модули (и цикл повторяется). То есть, выполнение переходит
из PHP в расширение, затем обратно в PHP, затем снова
в расширение. При втором вызове как раз важно не забыть
о том, что глобальные переменные первым вызовом
уже проинициализированы.
Ура, расширение работает!
15. Что выиграли в плане производительности?
Парсинг при помощи регулярных 100%
выражений
Парсинг при помощи расширения PHP 53%
XMLXSLT 36%
«чистый» PHP (компилирующий 3%
шаблонизатор)
парсинг при помощи регулярных выражений принят за 100%
16. Выводы?
Конечно, «чистый» PHP побеждает с чудовищным отрывом.
Но проблема создания компилирующего шаблонизатора
для движка с управляющим синтаксисом шаблона все еще
ждет своего героя…
Поэтому в итоге было принято решение использовать
в новой версии нашего продукта шаблонизацию XSLT:
дополнительный плюс состоит в том, что этот язык является
стандартом, с которым некоторые разработчики уже
знакомы, а не собственным «эксклюзивным» решением.
А расширение PHP, реализующее парсер «старого»
синтаксиса, помогло нам улучшить производительность
ранее написанных программных систем, уже работающих
у наших клиентов.
А еще теперь мы знаем, как сделать <?php asm mov ax,bx; ?>
17. Спасибо за внимание!
Вопросы?
Также можно обсудить в ЖЖ:
http://serge-index.livejournal.com