echo
做爲PHP中的語言結構, 常常會被使用, 所以瞭解他的實現仍是有必要的.php
版本 | 源碼地址 |
---|---|
PHP-7.2.8 |
https://github.com/php/php-src/tree/PHP-7.2.8/ |
void echo ( string $arg1 [, string $... ] )
複製代碼
echo
不是一個函數,是一個PHP的語言結構,所以不必定要使用小括號來指明參數,單引號、雙引號都行.echo
不表現得像一個函數,因此不能老是使用一個函數的上下文。echo
輸出多個字符串的時候, 不能使用小括號。echo
在php.ini中啓用short_open_tag
時,有一個快捷用法(view層)<?= 'Hello World'; ?>
echo
和echo
接受參數列表,而且沒有返回值。html
Note: 由於是一個語言構造器而不是一個函數,不能被 可變函數 調用。node
<?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') . '!';
複製代碼
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!
複製代碼
<?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 8git
輸出對象時彙報以上錯誤, 因此若是須要輸出對象, 必定要在其內部實現 __toString()
。github
<?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
複製代碼
echo tmpfile(); // Resource id #1
複製代碼
php
是一門腳本語言, 因此全部的符號都會先通過詞法解析和語法解析階段, 這兩個階段由lex
& yacc
完成。web
在計算機科學裏面,
lex
是一個產生詞法分析器的程序。Lex
經常與yacc
語法分析器產生程序一塊兒使用。Lex
是許多UNIX系統
的標準詞法分析器產生程序,並且這個工具所做的行爲被詳列爲POSIX標準
的一部分。Lex
讀進一個表明詞法分析器規則的輸入字符串流,而後輸出以C語言實作的詞法分析器源代碼。 --維基百科shell
對應的文件在 Zend/zend_language_parser.y
和 Zend/zend_language_scanner.l
。apache
<ST_IN_SCRIPTING>"echo" {
RETURN_TOKEN(T_ECHO);
}
複製代碼
ZEND引擎在讀取一個PHP文件以後會先進行詞法分析,就是用lex掃描,把對應的PHP字符轉換成相應的標記(也叫token),好比 echo $a;
在碰到這句首先會匹配到echo
,符合上面的規則,而後就返回一個 T_ECHO
標記,這個在後面的語法分析會用上,也就是在 zend_language_parser.y
文件中json
# %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
,再搜這個字符串,找到以下代碼:api
# 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; }
;
複製代碼
詞法分析後獲得單獨存在的詞塊不能表達完整的語義,還須要藉助規則進行組織串聯。語法分析器就是這個組織者。它會檢查語法、匹配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_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);
}
/* }}} */
複製代碼
不調用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實現.
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
的實現。
# 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;
複製代碼
echo
輸出大字符串(500K)的時候,執行時間會明顯變長,因此會被認爲PHP的echo
性能不好, 實際上這並非語言(PHP
)問題, 而是一個IO問題(IO的速度限制了輸出的速度)。
可是在某些時候echo
執行時間過長, 會影響其餘的服務, 進而影響整個系統。
那麼使用 apache
時如何優化使的 echo
變快, 讓PHP的請求處理過程儘快結束?
echo
慢是在等待「寫數據」成功返回, 因此可打開輸出緩存:
# 編輯php.ini
output_buffering = 4096 //bytes
# 調用ob_start()
ob_start();
echo $hugeString;
ob_end_flush();
複製代碼
ob_start()
會開闢一塊4096大小的buffer,因此若是$hugeString
大於 4096,將不會起到加速做用。
echo
會當即執行成功返回, 由於數據暫時寫到了咱們的輸出緩存中,若是buffer足夠大,那麼內容會等到腳本的最後,才一次性的發送給客戶端(嚴格的說是發給webserver)。
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;
}
/* }}} */
複製代碼
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 */);
}
}
/* }}} */
複製代碼
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;
}
複製代碼