Глубокое понимание эха PHP

задняя часть PHP GitHub исходный код

1 Обзор

echoВ качестве языковой структуры в PHP он используется часто, поэтому необходимо разобраться в его реализации.

Версия Адрес источника
PHP-7.2.8 https://github.com/php/php-src/tree/PHP-7.2.8/

2. Официальная документация (php.net)

2.1 Вывести одну или несколько строк

void echo ( string $arg1 [, string $... ] )

2.2 Описание

echoЭто не функция, это структура языка PHP, поэтому нет необходимости использовать круглые скобки для указания параметров, можно использовать одинарные или двойные кавычки.echoНе ведет себя как функция, поэтому не всегда может использовать контекст функции.echoПри выводе нескольких строк скобки использовать нельзя.echoвключить в php.inishort_open_tagПри использовании ярлыка (просмотр слоя)<?= 'Hello World'; ?> echoа такжеprintОсновное отличие состоит в том,echoПринимает список аргументов и не возвращает значения.

2.3 Примечания

Примечание. Поскольку это конструктор языка, а не функция, его нельзя вызвать функцией с переменным числом аргументов.

<?php

/**
 * Tip
 * 相对 echo 中拼接字符串而言,传递多个参数比较好,考虑到了 PHP 中连接运算符(“.”)的优先级。 传入多个参数,不需要圆括号保证优先级:
 */
echo "Sum: ", 1 + 2;
echo "Hello ", isset($name) ? $name : "John Doe", "!";

/** Tip
 * 如果是拼接的,相对于加号和三目元算符,连接运算符(“.”)具有更高优先级。为了正确性,必须使用圆括号:
 */
echo 'Sum: ' . (1 + 2);
echo 'Hello ' . (isset($name) ? $name : 'John Doe') . '!';

3. Применение

3.1 Выходные примитивные типы данных

echo 123, 'abc', [12, 34];  // 123abcArray

echo "Sum: ", 1 + 2; // Sum: 3
echo 'Sum: ' . (1 + 2); // Sum: 3

echo "Hello ", isset($name) ? $name : "John Doe", "!"; // Hello John Doe!
echo 'Hello ' . (isset($name) ? $name : 'John Doe') . '!'; // Hello John Doe!

3.2 Тип выходного объекта

<?php 
class Customer {
    public function say() {
        return 'Hello World';
    }
}

echo (new Customer());

Catchable fatal error: Object of class Customer could not be converted to string in /usercode/file.php on line 8

Вышеупомянутая ошибка сообщается при выводе объекта, поэтому, если вам нужно вывести объект, вы должны реализовать его внутри него.__toString().

<?php 
class Customer {
    public function say() {
        return 'Hello World';
    }
    
    /**
     * __toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
     */
    public function __toString(){
        return $this->say();
    }
}

echo (new Customer()); // Hello World

3.3 Тип выходного ресурса

echo tmpfile(); // Resource id #1

4. Исходный код

4.1 Обзор исходного кода

phpЭто язык сценариев, поэтому все символы сначала проходят этапы лексического анализа и синтаксического анализа, которые состоят изlex & yaccЗаканчивать.

В информатике,lexэто программа, которая производит лексический анализатор.Lexчасто сyaccИспользуется вместе с генератором парсеров.LexмногоUNIX系统Стандартный лексер создает программы для , и поведение, выполняемое этим инструментом, подробно описано какPOSIX标准часть.LexСчитайте входной поток строк, представляющий правила лексера, и выведите исходный код лексера, реализованный на C. --Википедия

Соответствующий файл находится вZend/zend_language_parser.yа такжеZend/zend_language_scanner.l.

4.2 Символ для разметки (Zend/zend_language_scanner.l)

<ST_IN_SCRIPTING>"echo" {
	RETURN_TOKEN(T_ECHO);
}

После того, как механизм ZEND прочитает файл PHP, он сначала выполнит лексический анализ, то есть использует lex для сканирования и преобразования соответствующих символов PHP в соответствующие токены (также называемые токенами), такие какecho $a;При встрече с этим предложением оно сначала будет соответствоватьecho, который соответствует приведенным выше правилам, а затем возвращаетT_ECHOМарка, это будет использоваться в следующем грамматическом разборе, т.zend_language_parser.yв файле

4.3 Разбор (Zend/zend_language_parser.y)

# %token Token就是一个个的“词块”
%token T_ECHO       "echo (T_ECHO)"

# statement T_ECHO echo_expr_list
statement:
		'{' inner_statement_list '}' { ? = $2; }
	|	if_stmt { ? = $1; }
	|	alt_if_stmt { ? = $1; }
	|	T_WHILE '(' expr ')' while_statement
			{ ? = zend_ast_create(ZEND_AST_WHILE, $3, $5); }
	|	T_DO statement T_WHILE '(' expr ')' ';'
			{ ? = zend_ast_create(ZEND_AST_DO_WHILE, $2, $5); }
	|	T_FOR '(' for_exprs ';' for_exprs ';' for_exprs ')' for_statement
			{ ? = zend_ast_create(ZEND_AST_FOR, $3, $5, $7, $9); }
	|	T_SWITCH '(' expr ')' switch_case_list
			{ ? = zend_ast_create(ZEND_AST_SWITCH, $3, $5); }
	|	T_BREAK optional_expr ';'		{ ? = zend_ast_create(ZEND_AST_BREAK, $2); }
	|	T_CONTINUE optional_expr ';'	{ ? = zend_ast_create(ZEND_AST_CONTINUE, $2); }
	|	T_RETURN optional_expr ';'		{ ? = zend_ast_create(ZEND_AST_RETURN, $2); }
	|	T_GLOBAL global_var_list ';'	{ ? = $2; }
	|	T_STATIC static_var_list ';'	{ ? = $2; }
	|	T_ECHO echo_expr_list ';'		{ ? = $2; }
	|	T_INLINE_HTML { ? = zend_ast_create(ZEND_AST_ECHO, $1); }
	|	expr ';' { ? = $1; }
	|	T_UNSET '(' unset_variables ')' ';' { ? = $3; }
	|	T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
			{ ? = zend_ast_create(ZEND_AST_FOREACH, $3, $5, NULL, $7); }
	|	T_FOREACH '(' expr T_AS foreach_variable T_DOUBLE_ARROW foreach_variable ')'
		foreach_statement
			{ ? = zend_ast_create(ZEND_AST_FOREACH, $3, $7, $5, $9); }
	|	T_DECLARE '(' const_list ')'
			{ zend_handle_encoding_declaration($3); }
		declare_statement
			{ ? = zend_ast_create(ZEND_AST_DECLARE, $3, $6); }
	|	';'	/* empty statement */ { ? = NULL; }
	|	T_TRY '{' inner_statement_list '}' catch_list finally_statement
			{ ? = zend_ast_create(ZEND_AST_TRY, $3, $5, $6); }
	|	T_THROW expr ';' { ? = zend_ast_create(ZEND_AST_THROW, $2); }
	|	T_GOTO T_STRING ';' { ? = zend_ast_create(ZEND_AST_GOTO, $2); }
	|	T_STRING ':' { ? = zend_ast_create(ZEND_AST_LABEL, $1); }
;

существуетstatementпилаT_ECHO, с последующимecho_expr_list, а затем найдите эту строку, чтобы найти следующий код:

# echo_expr_list
echo_expr_list:
    echo_expr_list ',' echo_expr { ? = zend_ast_list_add($1, $3); }
  | echo_expr { ? = zend_ast_create_list(1, ZEND_AST_STMT_LIST, $1); }
;

echo_expr:
  expr { ? = zend_ast_create(ZEND_AST_ECHO, $1); }
;

expr:
    variable              { ? = $1; }
  | expr_without_variable { ? = $1; }
;

После лексического анализа отдельные лексические блоки не могут выражать полную семантику, и их необходимо систематизировать и соединить с помощью правил. Парсер и есть этот органайзер. Он проверяет синтаксис, сопоставляет токены и связывает токены. В PHP7 результатом объединения организаций является абстрактное синтаксическое дерево (Abstract Syntax Tree,AST), пожалуйста, проверьте соответствующий исходный код для деталей:Абстрактное синтаксическое дерево (AST)

Это сложно понять таким образом.Далее давайте посмотрим на итоговое сгенерированное синтаксическое дерево на простом примере.

$a = 123;
$b = "hi~";

echo $a,$b;

Конкретный процесс синтаксического анализа здесь не будет объясняться. Если вам интересно, вы можете обратиться к zend_language_parse.y. Этот процесс не прост для понимания и требует понимания несколько раз. Окончательный сгенерированный ast выглядит следующим образом:

phpecho

4.4 Инициализация модуля (main/main.c)

пройти черезwrite_functionСвязывание функций вывода PHPphp_output_wrapperкzend_utility_functionsструктура, эта структура будет использоваться в xx

# php_module_startup

zend_utility_functions zuf;

// ...

gc_globals_ctor();

zuf.error_function = php_error_cb;
zuf.printf_function = php_printf;
zuf.write_function = php_output_wrapper;
zuf.fopen_function = php_fopen_wrapper_for_zend;
zuf.message_handler = php_message_handler_for_zend;
zuf.get_configuration_directive = php_get_configuration_directive_for_zend;
zuf.ticks_function = php_run_ticks;
zuf.on_timeout = php_on_timeout;
zuf.stream_open_function = php_stream_open_for_zend;
zuf.printf_to_smart_string_function = php_printf_to_smart_string;
zuf.printf_to_smart_str_function = php_printf_to_smart_str;
zuf.getenv_function = sapi_getenv;
zuf.resolve_path_function = php_resolve_path_for_zend;
zend_startup(&zuf, NULL);

zufЯвляетсяzend_utility_functionsструктуру, так чтоphp_output_wrapperфункция переданаzuf.write_function, сзади несколько слоев упаковки, и окончательная реализация тоже вmain/main.cВ файле реализована следующая функция:

/* {{{ php_output_wrapper
 */
static size_t php_output_wrapper(const char *str, size_t str_length)
{
	return php_output_write(str, str_length);
}

существуетphp_out_wrapperназывается вphp_output_writeсуществуетmain/output.cКод реализации выглядит следующим образом:

/* {{{ int php_output_write(const char *str, size_t len)
 * Buffered write 
 * #define PHP_OUTPUT_ACTIVATED        0x100000 
 * 当flags=PHP_OUTPUT_ACTIVATED,会调用sapi_module.ub_write输出, 每个SAPI都有自已的实现, cli中是调用sapi_cli_single_write()
 *  php_output_write(); //输出,有buffer, 调用php_output_op()
 *  php_output_write_unbuffered();//输出,没有buffer,调用PHP_OUTPUT_ACTIVATED,会调用sapi_module.ub_write
 *  php_output_set_status(); //用于SAPI设置output.flags
 *  php_output_get_status(); //获取output.flags的值
 */
PHPAPI size_t php_output_write(const char *str, size_t len)
{
	if (OG(flags) & PHP_OUTPUT_ACTIVATED) {
		php_output_op(PHP_OUTPUT_HANDLER_WRITE, str, len);
		return len;
	}
	if (OG(flags) & PHP_OUTPUT_DISABLED) {
		return 0;
	}
	return php_output_direct(str, len);
}
/* }}} */

4.5 Конец вывода (функция main/output.c fwrite)

Результат отсутствия вызова sapi_module

static size_t (*php_output_direct)(const char *str, size_t str_len) = php_output_stderr;

static size_t php_output_stderr(const char *str, size_t str_len)
{
	fwrite(str, 1, str_len, stderr);
/* See http://support.microsoft.com/kb/190351 */
#ifdef PHP_WIN32
	fflush(stderr);
#endif
	return str_len;
}

вывод вызова sapi_module

sapi_module.ub_write(context.out.data, context.out.used);

if (OG(flags) & PHP_OUTPUT_IMPLICITFLUSH) {
	sapi_flush();
}

php_output_opПодробная реализация выглядит следующим образом:

/* {{{ static void php_output_op(int op, const char *str, size_t len)
 * Output op dispatcher, passes input and output handlers output through the output handler stack until it gets written to the SAPI 
 */
static inline void php_output_op(int op, const char *str, size_t len)
{
	php_output_context context;
	php_output_handler **active;
	int obh_cnt;

	if (php_output_lock_error(op)) {
		return;
	}

	php_output_context_init(&context, op);

	/*
	 * broken up for better performance:
	 *  - apply op to the one active handler; note that OG(active) might be popped off the stack on a flush
	 *  - or apply op to the handler stack
	 */
	if (OG(active) && (obh_cnt = zend_stack_count(&OG(handlers)))) {
		context.in.data = (char *) str;
		context.in.used = len;

		if (obh_cnt > 1) {
			zend_stack_apply_with_argument(&OG(handlers), ZEND_STACK_APPLY_TOPDOWN, php_output_stack_apply_op, &context);
		} else if ((active = zend_stack_top(&OG(handlers))) && (!((*active)->flags & PHP_OUTPUT_HANDLER_DISABLED))) {
			php_output_handler_op(*active, &context);
		} else {
			php_output_context_pass(&context);
		}
	} else {
		context.out.data = (char *) str;
		context.out.used = len;
	}

	if (context.out.data && context.out.used) {
		php_output_header();

		if (!(OG(flags) & PHP_OUTPUT_DISABLED)) {
#if PHP_OUTPUT_DEBUG
			fprintf(stderr, "::: sapi_write('%s', %zu)\n", context.out.data, context.out.used);
#endif
			sapi_module.ub_write(context.out.data, context.out.used);

			if (OG(flags) & PHP_OUTPUT_IMPLICITFLUSH) {
				sapi_flush();
			}

			OG(flags) |= PHP_OUTPUT_SENT;
		}
	}
	php_output_context_dtor(&context);
}

Вышеприведенное понимает реализацию функции вывода PHP, а затем понимает реализацию эха.

4.6 Реализация выходного действия в движке ZEND (Zend/zend_vm_def.h)

ZEND_VM_HANDLER(40, ZEND_ECHO, CONST|TMPVAR|CV, ANY)
{
	USE_OPLINE
	zend_free_op free_op1;
	zval *z;

	SAVE_OPLINE();
	z = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);

	if (Z_TYPE_P(z) == IS_STRING) {
		zend_string *str = Z_STR_P(z);

		if (ZSTR_LEN(str) != 0) {
			zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
		}
	} else {
		zend_string *str = _zval_get_string_func(z);

		if (ZSTR_LEN(str) != 0) {
			zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
		} else if (OP1_TYPE == IS_CV && UNEXPECTED(Z_TYPE_P(z) == IS_UNDEF)) {
			GET_OP1_UNDEF_CV(z, BP_VAR_R);
		}
		zend_string_release(str);
	}

	FREE_OP1();
	ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

можно увидеть вzend vmпозвонивzend_writeЧтобы добиться результата, давайте посмотрим дальшеzend_writeреализация.

4.7 Реализация zend_write (Zend/zend.c)

# Zend/zend.h
typedef int (*zend_write_func_t)(const char *str, size_t str_length);

# Zend/zend.c
ZEND_API zend_write_func_t zend_write;

# 如下图所示, zend_write的初始化是在zend_startup()函数里面,这是zend引擎启动的时候需要做的一些初始化工作,有下面一句:

zend_write = (zend_write_func_t) utility_functions->write_function; // php_output_wrapper

zend_utility_functions *utility_functionsсуществуетmain/main.c php_module_startup()изzufопределяется в:

zuf.write_function = php_output_wrapper;

phpecho

5, ускорение php (эхо)

5.1 Действительно ли PHP-эхо работает медленно?

phpecho

echoПри выводе большой строки (500К) время выполнения будет значительно больше, поэтому будет считаться PHPechoПроизводительность плохая, на самом деле это не язык (PHP) проблема, но проблема ввода-вывода (скорость ввода-вывода ограничивает скорость вывода).

но в какой-то моментechoЕсли время выполнения слишком велико, это повлияет на другие службы, а затем на всю систему.

затем используйтеapacheкак оптимизироватьechoСделать это быстрее, чтобы процесс обработки запросов PHP завершался как можно быстрее?

5.2 еще можно оптимизировать: включаем кеш вывода

echoSlow ожидает успешного возврата «записи данных», поэтому вы можете открыть кеш вывода:

# 编辑php.ini
output_buffering = 4096 //bytes

# 调用ob_start()
ob_start();
echo $hugeString;
ob_end_flush();

ob_start()Буфер размером 4096 будет открыт, так что если$hugeStringЕсли больше 4096, эффекта ускорения не будет.

echoОн выполнится и вернётся сразу же, потому что данные временно записываются в наш кеш вывода.Если буфер достаточно большой, содержимое будет отправлено клиенту в один момент в конце скрипта (строго говоря, оно отправлено в веб-сервер).

6. Преобразование типов при выводе

6.1 Правила преобразования типов для вывода

input output desc code
Boolean String 1 или 0 echo true; // 1
Integer Integer не конвертировать echo 123; // 123
Float Float Не конвертируйте, обратите внимание на вопросы точности echo 123.234; // 123.234
String String не конвертировать echo 'abcd'; // abcd
Array Array - echo [12, 34]; // Array
Object Catchable fatal error Object of class stdClass could not be converted to string in file.php on line * echo json_decode(json_encode(['a' => 'b']));
Resource Resource id #1 - echo tmpfile(); // Resource id #1
NULL string Преобразовать в пустую строку echo null; // 空字符串

6.2 Исходный код преобразования типов для вывода (Zend/zend_operators.h и Zend/zend_operators.c)

# Zend/zend_operators.h
ZEND_API zend_string* ZEND_FASTCALL _zval_get_string_func(zval *op);

# Zend/zend_operators.c
ZEND_API zend_string* ZEND_FASTCALL _zval_get_string_func(zval *op) /* {{{ */
{
try_again:
	switch (Z_TYPE_P(op)) {
		case IS_UNDEF:
		case IS_NULL:
		case IS_FALSE:
			return ZSTR_EMPTY_ALLOC();
		case IS_TRUE:
			if (CG(one_char_string)['1']) {
				return CG(one_char_string)['1'];
			} else {
				return zend_string_init("1", 1, 0);
			}
		case IS_RESOURCE: {
			char buf[sizeof("Resource id #") + MAX_LENGTH_OF_LONG];
			int len;

			len = snprintf(buf, sizeof(buf), "Resource id #" ZEND_LONG_FMT, (zend_long)Z_RES_HANDLE_P(op));
			return zend_string_init(buf, len, 0);
		}
		case IS_LONG: {
			return zend_long_to_str(Z_LVAL_P(op));
		}
		case IS_DOUBLE: {
			return zend_strpprintf(0, "%.*G", (int) EG(precision), Z_DVAL_P(op));
		}
		case IS_ARRAY:
			zend_error(E_NOTICE, "Array to string conversion");
			return zend_string_init("Array", sizeof("Array")-1, 0);
		case IS_OBJECT: {
			zval tmp;
			if (Z_OBJ_HT_P(op)->cast_object) {
				if (Z_OBJ_HT_P(op)->cast_object(op, &tmp, IS_STRING) == SUCCESS) {
					return Z_STR(tmp);
				}
			} else if (Z_OBJ_HT_P(op)->get) {
				zval *z = Z_OBJ_HT_P(op)->get(op, &tmp);
				if (Z_TYPE_P(z) != IS_OBJECT) {
					zend_string *str = zval_get_string(z);
					zval_ptr_dtor(z);
					return str;
				}
				zval_ptr_dtor(z);
			}
			zend_error(EG(exception) ? E_ERROR : E_RECOVERABLE_ERROR, "Object of class %s could not be converted to string", ZSTR_VAL(Z_OBJCE_P(op)->name));
			return ZSTR_EMPTY_ALLOC();
		}
		case IS_REFERENCE:
			op = Z_REFVAL_P(op);
			goto try_again;
		case IS_STRING:
			return zend_string_copy(Z_STR_P(op));
		EMPTY_SWITCH_DEFAULT_CASE()
	}
	return NULL;
}
/* }}} */

7. Zend/zend_compile.c анализ эха

7.1 Адрес источника

7.2 zend_compile_exprвыполнить

# Zend/zend_compile.h
void zend_compile_expr(znode *node, zend_ast *ast);

# Zend/zend_compile.c
void zend_compile_expr(znode *result, zend_ast *ast) /* {{{ */
{
	/* CG(zend_lineno) = ast->lineno; */
	CG(zend_lineno) = zend_ast_get_lineno(ast);

	switch (ast->kind) {
		case ZEND_AST_ZVAL:
			ZVAL_COPY(&result->u.constant, zend_ast_get_zval(ast));
			result->op_type = IS_CONST;
			return;
		case ZEND_AST_ZNODE:
			*result = *zend_ast_get_znode(ast);
			return;
		case ZEND_AST_VAR:
		case ZEND_AST_DIM:
		case ZEND_AST_PROP:
		case ZEND_AST_STATIC_PROP:
		case ZEND_AST_CALL:
		case ZEND_AST_METHOD_CALL:
		case ZEND_AST_STATIC_CALL:
			zend_compile_var(result, ast, BP_VAR_R);
			return;
		case ZEND_AST_ASSIGN:
			zend_compile_assign(result, ast);
			return;
		case ZEND_AST_ASSIGN_REF:
			zend_compile_assign_ref(result, ast);
			return;
		case ZEND_AST_NEW:
			zend_compile_new(result, ast);
			return;
		case ZEND_AST_CLONE:
			zend_compile_clone(result, ast);
			return;
		case ZEND_AST_ASSIGN_OP:
			zend_compile_compound_assign(result, ast);
			return;
		case ZEND_AST_BINARY_OP:
			zend_compile_binary_op(result, ast);
			return;
		case ZEND_AST_GREATER:
		case ZEND_AST_GREATER_EQUAL:
			zend_compile_greater(result, ast);
			return;
		case ZEND_AST_UNARY_OP:
			zend_compile_unary_op(result, ast);
			return;
		case ZEND_AST_UNARY_PLUS:
		case ZEND_AST_UNARY_MINUS:
			zend_compile_unary_pm(result, ast);
			return;
		case ZEND_AST_AND:
		case ZEND_AST_OR:
			zend_compile_short_circuiting(result, ast);
			return;
		case ZEND_AST_POST_INC:
		case ZEND_AST_POST_DEC:
			zend_compile_post_incdec(result, ast);
			return;
		case ZEND_AST_PRE_INC:
		case ZEND_AST_PRE_DEC:
			zend_compile_pre_incdec(result, ast);
			return;
		case ZEND_AST_CAST:
			zend_compile_cast(result, ast);
			return;
		case ZEND_AST_CONDITIONAL:
			zend_compile_conditional(result, ast);
			return;
		case ZEND_AST_COALESCE:
			zend_compile_coalesce(result, ast);
			return;
		case ZEND_AST_PRINT:
			zend_compile_print(result, ast);
			return;
		case ZEND_AST_EXIT:
			zend_compile_exit(result, ast);
			return;
		case ZEND_AST_YIELD:
			zend_compile_yield(result, ast);
			return;
		case ZEND_AST_YIELD_FROM:
			zend_compile_yield_from(result, ast);
			return;
		case ZEND_AST_INSTANCEOF:
			zend_compile_instanceof(result, ast);
			return;
		case ZEND_AST_INCLUDE_OR_EVAL:
			zend_compile_include_or_eval(result, ast);
			return;
		case ZEND_AST_ISSET:
		case ZEND_AST_EMPTY:
			zend_compile_isset_or_empty(result, ast);
			return;
		case ZEND_AST_SILENCE:
			zend_compile_silence(result, ast);
			return;
		case ZEND_AST_SHELL_EXEC:
			zend_compile_shell_exec(result, ast);
			return;
		case ZEND_AST_ARRAY:
			zend_compile_array(result, ast);
			return;
		case ZEND_AST_CONST:
			zend_compile_const(result, ast);
			return;
		case ZEND_AST_CLASS_CONST:
			zend_compile_class_const(result, ast);
			return;
		case ZEND_AST_ENCAPS_LIST:
			zend_compile_encaps_list(result, ast);
			return;
		case ZEND_AST_MAGIC_CONST:
			zend_compile_magic_const(result, ast);
			return;
		case ZEND_AST_CLOSURE:
			zend_compile_func_decl(result, ast);
			return;
		default:
			ZEND_ASSERT(0 /* not supported */);
	}
}
/* }}} */

7.3 zend_compile_echoвыполнить

# Zend/zend_compile.c
void zend_compile_echo(zend_ast *ast) /* {{{ */
{
	zend_op *opline;
	zend_ast *expr_ast = ast->child[0];

	znode expr_node;
	zend_compile_expr(&expr_node, expr_ast);

	opline = zend_emit_op(NULL, ZEND_ECHO, &expr_node, NULL);
	opline->extended_value = 0;
}

8. Ссылка