摘要:方法用于一個類被當成字符串時應怎樣回應。此方法必須返回一個字符串,否則將發(fā)出一條級別的致命錯誤。常常與語法分析器產生程序一起使用。是許多系統(tǒng)的標準詞法分析器產生程序,而且這個工具所作的行為被詳列為標準的一部分。
1、概述
echo 作為PHP中的語言結構, 經(jīng)常會被使用, 因此了解他的實現(xiàn)還是有必要的.
版本 | 源碼地址 |
---|---|
PHP-7.2.8 | https://github.com/php/php-sr... |
void echo ( string $arg1 [, string $... ] )
文檔地址
2.2 說明echo 不是一個函數(shù),是一個PHP的語言結構,因此不一定要使用小括號來指明參數(shù),單引號、雙引號都行.2.3 注釋
echo 不表現(xiàn)得像一個函數(shù),所以不能總是使用一個函數(shù)的上下文。
echo 輸出多個字符串的時候, 不能使用小括號。
echo 在php.ini中啟用 short_open_tag 時,有一個快捷用法(view層)= "Hello World"; ?>
echo 和 print 最主要的不同之處是, echo 接受參數(shù)列表,并且沒有返回值。
Note: 因為是一個語言構造器而不是一個函數(shù),不能被 可變函數(shù) 調用。
3、應用 3.1 輸出基本數(shù)據(jù)類型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 輸出對象類型Catchable fatal error: Object of class Customer could not be converted to string in /usercode/file.php on line 8輸出對象時匯報以上錯誤, 所以如果需要輸出對象, 一定要在其內部實現(xiàn) __toString()。
say(); } } echo (new Customer()); // Hello World3.3 輸出資源類型echo tmpfile(); // Resource id #14、源碼 4.1 源碼概述php 是一門腳本語言, 所以所有的符號都會先經(jīng)過詞法解析和語法解析階段, 這兩個階段由lex & yacc 完成。
在計算機科學里面,lex是一個產生詞法分析器的程序。 Lex常常與yacc 語法分析器產生程序一起使用。Lex是許多UNIX系統(tǒng)的標準詞法分析器產生程序,而且這個工具所作的行為被詳列為POSIX標準的一部分。 Lex讀進一個代表詞法分析器規(guī)則的輸入字符串流,然后輸出以C語言實做的詞法分析器源代碼。 --維基百科對應的文件在 Zend/zend_language_parser.y 和 Zend/zend_language_scanner.l。
4.2 字符轉標記(Zend/zend_language_scanner.l)"echo" { RETURN_TOKEN(T_ECHO); } ZEND引擎在讀取一個PHP文件之后會先進行詞法分析,就是用lex掃描,把對應的PHP字符轉換成相應的標記(也叫token),比如 echo $a; 在碰到這句首先會匹配到echo,符合上面的規(guī)則,然后就返回一個 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; } ;詞法分析后得到多帶帶存在的詞塊不能表達完整的語義,還需要借助規(guī)則進行組織串聯(lián)。語法分析器就是這個組織者。它會檢查語法、匹配Token,對Token進行關聯(lián)。PHP7中,組織串聯(lián)的產物就是抽象語法樹(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輸出函 數(shù)php_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函數(shù)傳給了zuf.write_function,后面還有好幾層包裝,最后的實現(xiàn)也是在main/main.c文件里面實現(xiàn)的,是下面這個函數(shù):
/* {{{ 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 中實現(xiàn), 實現(xiàn)代碼如下:
/* {{{ 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都有自已的實現(xiàn), 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函數(shù))不調用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 詳細實現(xiàn)如下:
/* {{{ 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) ", 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輸出函數(shù)的實現(xiàn), 接下來了解echo實現(xiàn).
4.6 輸出動作的ZEND引擎實現(xiàn)(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來實現(xiàn)輸出,接下來看下zend_write的實現(xiàn)。
4.7 zend_write實現(xiàn)(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()函數(shù)里面,這是zend引擎啟動的時候需要做的一些初始化工作,有下面一句: zend_write = (zend_write_func_t) utility_functions->write_function; // php_output_wrapperzend_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)的時候,執(zhí)行時間會明顯變長,所以會被認為PHP的echo性能很差, 實際上這并不是語言(PHP)問題, 而是一個IO問題(IO的速度限制了輸出的速度)。
但是在某些時候echo執(zhí)行時間過長, 會影響其他的服務, 進而影響整個系統(tǒng)。
那么使用 apache 時如何優(yōu)化使的 echo 變快, 讓PHP的請求處理過程盡快結束?
5.2 還是可以優(yōu)化的: 打開輸出緩存echo慢是在等待“寫數(shù)據(jù)”成功返回, 所以可打開輸出緩存:
# 編輯php.ini output_buffering = 4096 //bytes # 調用ob_start() ob_start(); echo $hugeString; ob_end_flush();ob_start() 會開辟一塊4096大小的buffer,所以如果 $hugeString 大于 4096,將不會起到加速作用。echo 會立即執(zhí)行成功返回, 因為數(shù)據(jù)暫時寫到了我們的輸出緩存中,如果buffer足夠大,那么內容會等到腳本的最后,才一次性的發(fā)送給客戶端(嚴格的說是發(fā)給webserver)。
6、輸出時的類型轉換 6.1 輸出時的類型轉換規(guī)則
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; // 空字符串 |
# 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對echo的解析 7.1 源碼地址
PHP源碼地址 zend_compile.h
PHP源碼地址 zend_compile.c
7.2 zend_compile_expr 實現(xiàn)# 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 實現(xiàn)
# 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、參考
@Laruence 加速PHP的ECHO
@Laruence PHP是無辜的
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/29141.html
摘要:設計模式系列之入門設計模式是一套被反復使用多數(shù)人知曉的經(jīng)過分類編目的代碼設計經(jīng)驗的總結。毫無疑問,設計模式于己于他人于系統(tǒng)都是多贏的設計模式使代碼編制真正工程化設計模式是軟件工程的基石脈絡,如同大廈的結構一樣。 PHP設計模式系列之入門 設計模式(Design pattern)是一套被反復使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設計經(jīng)驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易...
摘要:閉包閉包是什么閉包和匿名函數(shù)在中被引入。可以將匿名函數(shù)和閉包視作相同的概念。閉包和字符串或整數(shù)一樣,是一等值類型。何時使用我們通常把閉包當做函數(shù)和方法的回調使用。 閉包 閉包是什么? 1).閉包和匿名函數(shù)在PHP5.3中被引入。2).閉包是指在創(chuàng)建時封裝函數(shù)周圍狀態(tài)的函數(shù),即使閉包所在的環(huán)境不存在了,閉包封裝的狀態(tài)依然存在,這一點和Javascript的閉包特性很相似。3).匿名函數(shù)就...
摘要:在安裝及各種擴展的過程中,如果你是用源碼安裝,會注意到在成功之后總會有一句提示這個就是運行的自動化測試了。本文轉自官方博客 在安裝 PHP7 及各種擴展的過程中,如果你是用源碼安裝,會注意到在 make 成功之后總會有一句提示:Dont forget to run make test. 這個 make test 就是運行 PHP 的自動化測試了。 如果你剛剛安裝完 PHP7,直接運行 ...
閱讀 635·2021-11-22 15:32
閱讀 2723·2021-11-19 09:40
閱讀 2318·2021-11-17 09:33
閱讀 1274·2021-11-15 11:36
閱讀 1870·2021-10-11 10:59
閱讀 1482·2019-08-29 16:41
閱讀 1785·2019-08-29 13:45
閱讀 2155·2019-08-26 13:36