28 February 2013

Кастуем магией PHP

PHP
Recovery mode
PHP замечательный язык программирования. При всех его недостатках он не перестает удивлять. Недавно столкнулся со следующим — на первый взгляд загадочным — его поведением.

Как известно PHP имеет встроенный шаблонизатор. Весь текст, который интерпретатор встречает между тегами обозначающими конец и начало PHP кода, он отправляет в буфер вывода. Убедиться в этом можно на следующем примере:

<?php
echo "Hello, ";
?>

World.

<?php

echo "All is fine\n";


Выводом программы будет «Hello, World. All is fine», что и следовало ожидать. Но что происходит на самом деле? Посмотрим на другой пример:

<?php
$three = function() {
?>
        Three
<?php
};

$one = function() {
?>
        One
<?php
};

$two = function() {
?>
        Two
<?php
};

$one();
$two();
$three();

Если выполнить исходный код, то выводом программы будет «One Two Three», что немного странно. Ведь текст в коде встречался совсем в другой последовательности и в буфер вывода должно было попасть «Three One Two».

На самом деле PHP не отправляет текст в буфер вывода как только он его встречает. В интерпретаторе языка есть специальный опкод ZEND_ECHO (в этот опкод транслируется echo) и кусок текста между PHP кодом будут транслироваться в аргумент этого опкода. Именно поэтому у нас текст во втором примере выводиться в той последовательности в которой мы вызываем созданные анонимные функции (вывод текста стал частью анонимных функций благодаря опкоду ZEND_ECHO.

В подтверждении моих слов кусочек содержимого файла zend_language_parser.y

	|	T_INLINE_HTML			{ zend_do_echo(&$1 TSRMLS_CC); }

И реализация самой функции zend_do_echo из zend_compile.c:

void zend_do_echo(const znode *arg TSRMLS_DC) /* {{{ */
{
	zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);

	opline->opcode = ZEND_ECHO;
	SET_NODE(opline->op1, arg);
	SET_UNUSED(opline->op2);
}
/* }}} */

Ну и какой от этого толк?

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

$c->header->addClass('header')->setContent(function() {
?>

	<ul>
            <?= $c->li->addClassIf(1, 'active')->setContent('Main') ;?>
            <?= $c->li->addClassIf(0, 'active')->setContent('Account') ;?>
            <?= $c->li->addClassIf(0, 'active')->setContent('FAQ') ;?>
            <?= $c->li->addClassIf(0, 'active')->setContent('Logout') ;?>            
        </ul>

<?php
})->_print();
?>

Где в переменой $c->header объект класса CElement. Удобно? На вкус и цвет товарищей нет)

P.S.
Из официальной документации поведение интерпретатора не совсем очевидно. Более того в ней совсем не упоминается о преобразовании текста в аргумент опкода: when the PHP interpreter hits the ?> closing tags, it simply starts outputting whatever it finds (except for an immediately following newline — see instruction separation) until it hits another opening tag
Tags:phpzend engineweb-разработка
Hubs: PHP
-31
5.9k 17
Comments 22
Popular right now
PHP-разработчик (Middle PHP Developer)
from 70,000 to 100,000 ₽Skill CupТамбовRemote job
Web-разработчик (PHP)
from 30,000 to 40,000 ₽OpenTrade CommerceRemote job
PHP-разработчик / Web-программист
from 80,000 ₽WeCan ITЧелябинскRemote job
Web программист/PHP разработчик (senior)
from 150,000 to 250,000 ₽ENOT.ioКазань
PHP developer
to 250,000 ₽РНКБ Банк (ПАО)МоскваRemote job