深入理解php原理之include include_once require require_once

来源:互联网 发布:linux shell怎么退行 编辑:程序博客网 时间:2024/05/29 14:50
先看下总结:
require include php引擎是直接调用compile_filename来实现的
在compile_filename实现里面会把把resolved_path变量加入included_files数组中
 
而include_once,require_once 则是先通过zend_resolve_path得到一个解析过的路径(resolved_path)
然后通过zend_hash_exists查找included_files变量,如果找到则说明已经加载过
否则通过zend_stream_open判断文件是否存在,如果存在:1,把resolved_path变量加入included_files数组中 2,通过zend_compile_file调用刚才打开的文件指针进行编译
不存在则通过zend_message_dispatcher派发一个消息,然后分别处理
 
每次调用include_once,require_once都会进行路径解析(zend_resolve_path),而绝对路径都是直接返回,速度最快
 
历史:
在php5.2之前的源码,include_once,require_once都会执行zend_stream_open也就是每次都会打开文件,然后再和included_files比较
所以5.2之后的实现,相同文件加载调用include_once,require_once是没有io操作的
 
 
 
分析过程
1,查看php语法定义文件Zend/zend_language_scanner.l
找到
<ST_IN_SCRIPTING>"include" {
return T_INCLUDE;
}
 
<ST_IN_SCRIPTING>"include_once" {
return T_INCLUDE_ONCE;
}
 
<ST_IN_SCRIPTING>"require" {
return T_REQUIRE;
}
 
<ST_IN_SCRIPTING>"require_once" {
return T_REQUIRE_ONCE;
}
 
可以看到被定义成T_INCLUDE T_INCLUDE_ONCE T_REQUIRE T_REQUIRE_ONCE四个标记(token)
 
 
2,查看php语法分析文件Zend/zend_language_parser.y
找到
|T_INCLUDE expr { zend_do_include_or_eval(ZEND_INCLUDE, &$$, &$2 TSRMLS_CC); }
|T_INCLUDE_ONCE expr { zend_do_include_or_eval(ZEND_INCLUDE_ONCE, &$$, &$2 TSRMLS_CC); }
|T_REQUIRE expr{ zend_do_include_or_eval(ZEND_REQUIRE, &$$, &$2 TSRMLS_CC); }
|T_REQUIRE_ONCE expr{ zend_do_include_or_eval(ZEND_REQUIRE_ONCE, &$$, &$2 TSRMLS_CC); }
 
可以看见四个函数都是用zend_do_include_or_eval实现的,只是参数不一样
 
 
3,搜索下源码,在Zend/zend_compile.c 找到函数实现
void zend_do_include_or_eval(int type, znode *result, const znode *op1 TSRMLS_DC) /* {{{ */
{
zend_do_extended_fcall_begin(TSRMLS_C);
{
zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
 
opline->opcode = ZEND_INCLUDE_OR_EVAL;
opline->result_type = IS_VAR;
opline->result.var = get_temporary_variable(CG(active_op_array));
SET_NODE(opline->op1, op1);
SET_UNUSED(opline->op2);
opline->extended_value = type;
GET_NODE(result, opline->result);
}
zend_do_extended_fcall_end(TSRMLS_C);
}
 
这里是把opcode设置为ZEND_INCLUDE_OR_EVAL
 
 
4,在zend_vm_def.h找到ZEND_INCLUDE_OR_EVAL的定义
ZEND_VM_HANDLER(73, ZEND_INCLUDE_OR_EVAL, CONST|TMP|VAR|CV, ANY)
{
USE_OPLINE
zend_op_array *new_op_array=NULL;
zend_free_op free_op1;
zval *inc_filename;
    zval *tmp_inc_filename = NULL;
zend_bool failure_retval=0;
 
SAVE_OPLINE();
inc_filename = GET_OP1_ZVAL_PTR(BP_VAR_R);
 
if (inc_filename->type!=IS_STRING) {
MAKE_STD_ZVAL(tmp_inc_filename);
ZVAL_COPY_VALUE(tmp_inc_filename, inc_filename);
zval_copy_ctor(tmp_inc_filename);
convert_to_string(tmp_inc_filename);
inc_filename = tmp_inc_filename;
}
 
if (opline->extended_value != ZEND_EVAL && strlen(Z_STRVAL_P(inc_filename)) != Z_STRLEN_P(inc_filename)) {
if (opline->extended_value == ZEND_INCLUDE_ONCE || opline->extended_value == ZEND_INCLUDE) {
zend_message_dispatcher(ZMSG_FAILED_INCLUDE_FOPEN, Z_STRVAL_P(inc_filename) TSRMLS_CC);
} else {
zend_message_dispatcher(ZMSG_FAILED_REQUIRE_FOPEN, Z_STRVAL_P(inc_filename) TSRMLS_CC);
}
} else {
switch (opline->extended_value) {
case ZEND_INCLUDE_ONCE:
case ZEND_REQUIRE_ONCE: {
zend_file_handle file_handle;
char *resolved_path;
 
resolved_path = zend_resolve_path(Z_STRVAL_P(inc_filename), Z_STRLEN_P(inc_filename) TSRMLS_CC);
if (resolved_path) {
failure_retval = zend_hash_exists(&EG(included_files), resolved_path, strlen(resolved_path)+1);
} else {
resolved_path = Z_STRVAL_P(inc_filename);
}
 
if (failure_retval) {
/* do nothing, file already included */
} else if (SUCCESS == zend_stream_open(resolved_path, &file_handle TSRMLS_CC)) {
 
if (!file_handle.opened_path) {
file_handle.opened_path = estrdup(resolved_path);
}
 
if (zend_hash_add_empty_element(&EG(included_files), file_handle.opened_path, strlen(file_handle.opened_path)+1)==SUCCESS) {
new_op_array = zend_compile_file(&file_handle, (opline->extended_value==ZEND_INCLUDE_ONCE?ZEND_INCLUDE:ZEND_REQUIRE) TSRMLS_CC);
zend_destroy_file_handle(&file_handle TSRMLS_CC);
} else {
zend_file_handle_dtor(&file_handle TSRMLS_CC);
failure_retval=1;
}
} else {
if (opline->extended_value == ZEND_INCLUDE_ONCE) {
zend_message_dispatcher(ZMSG_FAILED_INCLUDE_FOPEN, Z_STRVAL_P(inc_filename) TSRMLS_CC);
} else {
zend_message_dispatcher(ZMSG_FAILED_REQUIRE_FOPEN, Z_STRVAL_P(inc_filename) TSRMLS_CC);
}
}
if (resolved_path != Z_STRVAL_P(inc_filename)) {
efree(resolved_path);
}
}
break;
case ZEND_INCLUDE:
case ZEND_REQUIRE:
new_op_array = compile_filename(opline->extended_value, inc_filename TSRMLS_CC);
break;
case ZEND_EVAL: {
char *eval_desc = zend_make_compiled_string_description("eval()'d code" TSRMLS_CC);
 
new_op_array = zend_compile_string(inc_filename, eval_desc TSRMLS_CC);
efree(eval_desc);
}
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
}
if (tmp_inc_filename) {
zval_ptr_dtor(&tmp_inc_filename);
}
FREE_OP1();
if (UNEXPECTED(EG(exception) != NULL)) {
HANDLE_EXCEPTION();
} else if (EXPECTED(new_op_array != NULL)) {
EX(original_return_value) = EG(return_value_ptr_ptr);
EG(active_op_array) = new_op_array;
if (RETURN_VALUE_USED(opline)) {
EX_T(opline->result.var).var.ptr = NULL;
EX_T(opline->result.var).var.ptr_ptr = &EX_T(opline->result.var).var.ptr;
EG(return_value_ptr_ptr) = EX_T(opline->result.var).var.ptr_ptr;
} else {
EG(return_value_ptr_ptr) = NULL;
}
 
EX(current_object) = EX(object);
 
EX(function_state).function = (zend_function *) new_op_array;
EX(object) = NULL;
 
if (!EG(active_symbol_table)) {
zend_rebuild_symbol_table(TSRMLS_C);
}
 
if (EXPECTED(zend_execute == execute)) {
ZEND_VM_ENTER();
} else {
zend_execute(new_op_array TSRMLS_CC);
}
 
EX(function_state).function = (zend_function *) EX(op_array);
EX(object) = EX(current_object);
 
EG(opline_ptr) = &EX(opline);
EG(active_op_array) = EX(op_array);
EG(return_value_ptr_ptr) = EX(original_return_value);
destroy_op_array(new_op_array TSRMLS_CC);
efree(new_op_array);
if (UNEXPECTED(EG(exception) != NULL)) {
zend_throw_exception_internal(NULL TSRMLS_CC);
HANDLE_EXCEPTION();
} else if (RETURN_VALUE_USED(opline)) {
if (!EX_T(opline->result.var).var.ptr) { /* there was no return statement */
zval *retval;
 
ALLOC_ZVAL(retval);
ZVAL_BOOL(retval, 1);
INIT_PZVAL(retval);
EX_T(opline->result.var).var.ptr = retval;
}
}
 
} else if (RETURN_VALUE_USED(opline)) {
zval *retval;
 
ALLOC_ZVAL(retval);
ZVAL_BOOL(retval, failure_retval);
INIT_PZVAL(retval);
AI_SET_PTR(&EX_T(opline->result.var), retval);
}
ZEND_VM_NEXT_OPCODE();
}
 
5,在Zend/zend.c的zend_startup函数中找到对zend_resolve_path的赋值
zend_resolve_path = utility_functions->resolve_path_function;
也就是调用utility_functions的成员,而这个utility_functions是在php初始化(php_module_startup)的时候赋值的
 
 
总结:
require include php引擎是直接调用compile_filename来实现的
在compile_filename实现里面会把把resolved_path变量加入included_files数组中
 
而include_once,require_once 则是先通过zend_resolve_path得到一个解析过的路径(resolved_path)
然后通过zend_hash_exists查找included_files变量,如果找到则说明已经加载过
否则通过zend_stream_open判断文件是否存在,如果存在:1,把resolved_path变量加入included_files数组中 2,通过zend_compile_file调用刚才打开的文件指针进行编译
不存在则通过zend_message_dispatcher派发一个消息,然后分别处理
 
每次调用include_once,require_once都会进行路径解析(zend_resolve_path),而绝对路径都是直接返回,速度最快
 
历史:
在php5.2之前的源码,include_once,require_once都会执行zend_stream_open也就是每次都会打开文件,然后再和included_files比较
所以5.2之后的实现,相同文件加载调用include_once,require_once是没有io操作的
 
附:
路径查询原则(手册上的)
寻找包含文件的顺序先是在当前工作目录的相对的 include_path 下寻找,然后是当前运行脚本所在目录相对的 include_path 下寻找。
例如 include_path 是 .,当前工作目录是 /www/,脚本中要 include 一个 include/a.php 并且在该文件中有一句 include "b.php",则寻找 b.php 的顺序先是 /www/,然后是 /www/include/。如果文件名以 ./ 或者 ../ 开始,则只在当前工作目录相对的 include_path 下寻找
 
 
5.1.6的实现
if (SUCCESS == zend_stream_open(inc_filename->value.str.val, &file_handle TSRMLS_CC)) {
 
if (!file_handle.opened_path) {
file_handle.opened_path = estrndup(inc_filename->value.str.val, inc_filename->value.str.len);
}
 
if (zend_hash_add(&EG(included_files), file_handle.opened_path, strlen(file_handle.opened_path)+1, (void *)&dummy, sizeof(int), NULL)==SUCCESS) {
new_op_array = zend_compile_file(&file_handle, (opline->op2.u.constant.value.lval==ZEND_INCLUDE_ONCE?ZEND_INCLUDE:ZEND_REQUIRE) TSRMLS_CC);
zend_destroy_file_handle(&file_handle TSRMLS_CC);
} else {
zend_file_handle_dtor(&file_handle);
failure_retval=1;
}
}
0 0
原创粉丝点击