1、文檔(VeVb.com)
1.1 輸出一個或多個字符串
void echo ( string $arg1 [, string $... ] )
1.2 說明
echo 不是一個函數,是一個PHP的語言結構,因此不一定要使用小括號來指明參數,單引號、雙引號都行.
echo 不表現得像一個函數,所以不能總是使用一個函數的上下文。
echo 輸出多個字符串的時候, 不能使用小括號。
echo 在php.ini中啟用 short_open_tag 時,有一個快捷用法(view層) ?= Hello World ?
echo 和 print 最主要的不同之處是, echo 接受參數列表,并且沒有返回值。
1.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 ) . !
2、應用
2.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!
2.2 輸出對象類型
?php html' target='_blank'>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
2.3 輸出資源類型
echo tmpfile(); // Resource id #1
3、源碼
3.1 源碼概述
php 是一門腳本語言, 所以所有的符號都會先經過詞法解析和語法解析階段, 這兩個階段由lex yacc 完成。
在計算機科學里面,lex是一個產生詞法分析器的程序。 Lex常常與yacc 語法分析器產生程序一起使用。Lex是許多UNIX系統的標準詞法分析器產生程序,而且這個工具所作的行為被詳列為POSIX標準的一部分。 Lex讀進一個代表詞法分析器規則的輸入字符串流,然后輸出以C語言實做的詞法分析器源代碼。
對應的文件在 Zend/zend_language_parser.y 和 Zend/zend_language_scanner.l。
3.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 文件中
3.3 語法分析(Zend/zend_language_parser.y)
# %token Token就是一個個的“詞塊”%token T_ECHO echo (T_ECHO) # statement T_ECHO echo_expr_liststatement: { 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_listecho_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; };
詞法分析后得到單獨存在的詞塊不能表達完整的語義,還需要借助規則進行組織串聯。語法分析器就是這個組織者。它會檢查語法、匹配Token,對Token進行關聯。PHP7中,組織串聯的產物就是抽象語法樹(Abstract Syntax Tree,AST), 詳情請查看相關源碼: 抽象語法樹(Abstract Syntax Tree,AST)
這么看比較難理解,接下來我們從一個簡單的例子看下最終生成的語法樹。
$a = 123;$b = hi~ echo $a,$b;
具體解析過程這里不再解釋,有興趣的可以翻下zend_language_parse.y中,這個過程不太容易理解,需要多領悟幾遍,最后生成的ast如下圖:
通過 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_wrapperstatic 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);/* }}} */
3.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實現.
3.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的實現。
3.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;
4、php(echo)加速
4.1 PHP echo真的慢么?
echo 輸出大字符串(500K)的時候,執行時間會明顯變長,所以會被認為PHP的echo性能很差, 實際上這并不是語言(PHP)問題, 而是一個IO問題(IO的速度限制了輸出的速度)。
但是在某些時候echo執行時間過長, 會影響其他的服務, 進而影響整個系統。
那么使用 apache 時如何優化使的 echo 變快, 讓PHP的請求處理過程盡快結束?
4.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)。
5、輸出時的類型轉換
5.1輸出時的類型轉換規則
inputoutputdesccodeBooleanString1 或 0echo true; // 1IntegerInteger不轉換echo 123; // 123FloatFloat不轉換, 注意精度問題echo 123.234; // 123.234StringString不轉換echo abcd // abcdArrayArray-echo [12, 34]; // ArrayObjectCatchable fatal errorObject of class stdClass could not be converted to string in file.php on line *echo json_decode(json_encode([ a = b ]));ResourceResource id #1-echo tmpfile(); // Resource id #1NULLstring轉為空字符串echo null; // 空字符串5.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;/* }}} */
6、Zend/zend_compile.c對echo的解析
6.1 源碼地址
PHP源碼地址 zend_compile.h
PHP源碼地址 zend_compile.c
6.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 */);/* }}} */
6.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編程中echo用逗號和用點號連接的區別,echo逗號
php echo 輸出字符串函數詳解
以上就是PHP中語言結構echo的深入解析(推薦)的詳細內容,PHP教程
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。
新聞熱點
疑難解答