概述详解PHP中的echo语句 本篇文章带大家深入理解PHP中的echo语句。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。
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 $... ] )
文档地址:http://PHP.net/manual/zh/function.echo.PHP@H_403_47@2.2 说明
echo
不是一个函数,是一个PHP的语言结构,因此不一定要使用小括号来指明参数,单引号、双引号都行.echo
不表现得像一个函数,所以不能总是使用一个函数的上下文。echo
输出多个字符串的时候, 不能使用小括号。echo
在PHP.ini中启用short_open_tag
时,有一个快捷用法(vIEw层)<?= 'Hello World'; ?>
echo
和echo
接受参数列表,并且没有返回值。
2.3 注释
Note: 因为是一个语言构造器而不是一个函数,不能被 可变函数 调用。
<?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]; // 123abcArrayecho "Sum: ", 1 + 2; // Sum: 3echo 'Sum: ' . (1 + 2); // Sum: 3echo "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字符转换成相应的标记(也叫token),比如 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_Liststatement: '{' inner_statement_List '}' { ? = ; } | if_stmt { ? = ; } | alt_if_stmt { ? = ; } | T_WHILE '(' expr ')' while_statement { ? = Zend_ast_create(ZenD_AST_WHILE, , ); } | T_DO statement T_WHILE '(' expr ')' ';' { ? = Zend_ast_create(ZenD_AST_DO_WHILE, , ); } | T_FOR '(' for_exprs ';' for_exprs ';' for_exprs ')' for_statement { ? = Zend_ast_create(ZenD_AST_FOR, , , , ); } | T_SWITCH '(' expr ')' switch_case_List { ? = Zend_ast_create(ZenD_AST_SWITCH, , ); } | T_BREAK optional_expr ';' { ? = Zend_ast_create(ZenD_AST_BREAK, ); } | T_CONTINUE optional_expr ';' { ? = Zend_ast_create(ZenD_AST_CONTINUE, ); } | T_RETURN optional_expr ';' { ? = Zend_ast_create(ZenD_AST_RETURN, ); } | T_GLOBAL global_var_List ';' { ? = ; } | T_STATIC static_var_List ';' { ? = ; } | T_ECHO echo_expr_List ';' { ? = ; } | T_INliNE_HTML { ? = Zend_ast_create(ZenD_AST_ECHO, ); } | expr ';' { ? = ; } | T_UNSET '(' unset_variables ')' ';' { ? = ; } | T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement { ? = Zend_ast_create(ZenD_AST_FOREACH, , , NulL, ); } | T_FOREACH '(' expr T_AS foreach_variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement { ? = Zend_ast_create(ZenD_AST_FOREACH, , , , ); } | T_DECLARE '(' const_List ')' { Zend_handle_enCoding_declaration(); } declare_statement { ? = Zend_ast_create(ZenD_AST_DECLARE, , ); } | ';' /* empty statement */ { ? = NulL; } | T_TRY '{' inner_statement_List '}' catch_List finally_statement { ? = Zend_ast_create(ZenD_AST_TRY, , , ); } | T_THROW expr ';' { ? = Zend_ast_create(ZenD_AST_THROW, ); } | T_GOTO T_STRING ';' { ? = Zend_ast_create(ZenD_AST_GOTO, ); } | T_STRING ':' { ? = Zend_ast_create(ZenD_AST_LABEL, ); };
在 statement
看到了 T_ECHO
, 后面跟着 echo_expr_List
,再搜这个字符串,找到如下代码:
# echo_expr_Listecho_expr_List: echo_expr_List ',' echo_expr { ? = Zend_ast_List_add(, ); } | echo_expr { ? = Zend_ast_create_List(1, ZenD_AST_STMT_List, ); };echo_expr: expr { ? = Zend_ast_create(ZenD_AST_ECHO, ); };expr: variable { ? = ; } | expr_without_variable { ? = ; };
词法分析后得到单独存在的词块不能表达完整的语义,还需要借助规则进行组织串联。语法分析器就是这个组织者。它会检查语法、匹配Token,对Token进行关联。PHP7中,组织串联的产物就是抽象语法树(Abstract Syntax Tree
,AST
), 详情请查看相关源码: 抽象语法树(Abstract Syntax Tree,AST)
这么看比较难理解,接下来我们从一个简单的例子看下最终生成的语法树。
$a = 123;$b = "hi~";echo $a,$b;
具体解析过程这里不再解释,有兴趣的可以翻下Zend_language_parse.y中,这个过程不太容易理解,需要多领悟几遍,最后生成的ast如下图:
4.4 模块初始化(main/main.c)
通过 write_function
绑定PHP输出函 数PHP_output_wrapper
至 Zend_utility_functions
结构体, 此结构体会在xx被使用
# PHP_module_startupZend_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输出函数的实现, 接下来了解echo实现.
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.htypedef int (*Zend_write_func_t)(const char *str, size_t str_length);# Zend/Zend.cZenD_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;
5、PHP(echo)加速5.1 PHP echo 真的慢么?
echo
输出大字符串(500K)的时候,执行时间会明显变长,所以会被认为PHP的echo
性能很差, 实际上这并不是语言(PHP
)问题, 而是一个IO问题(IO的速度限制了输出的速度)。
但是在某些时候echo
执行时间过长, 会影响其他的服务, 进而影响整个系统。
那么使用 apache
时如何优化使的 echo
变快, 让PHP的请求处理过程尽快结束?
5.2 还是可以优化的: 打开输出缓存
echo
慢是在等待“写数据”成功返回, 所以可打开输出缓存:
# 编辑PHP.inioutput_buffering = 4096 //bytes# 调用ob_start()ob_start();echo $hugeString;ob_end_flush();
ob_start()
会开辟一块4096大小的buffer,所以如果$hugeString
大于 4096,将不会起到加速作用。
echo
会立即执行成功返回, 因为数据暂时写到了我们的输出缓存中,如果buffer足够大,那么内容会等到脚本的最后,才一次性的发送给客户端(严格的说是发给webserver)。
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.hZenD_API Zend_string* ZenD_FASTCALL _zval_get_string_func(zval *op);# Zend/Zend_operators.cZenD_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对echo的解析7.1 源码地址PHP源码地址 zend_compile.hPHP源码地址 zend_compile.c@H_403_47@7.2 Zend_compile_expr
实现
# Zend/Zend_compile.hvoID Zend_compile_expr(znode *node, Zend_ast *ast);# Zend/Zend_compile.cvoID 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.cvoID 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;}
推荐学习:《PHP视频教程》 总结
以上是内存溢出为你收集整理的详解PHP中的echo语句全部内容,希望文章能够帮你解决详解PHP中的echo语句所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
请登录后查看评论内容