这里有新鲜出炉的 PHP 设计模式,程序狗速度看过来!
PHP(外文名: Hypertext Preprocessor,中文名:"超文本预处理器")是一种通用开源脚本语言。语法吸收了 C 语言、Java 和 Perl 的特点,入门门槛较低,易于学习,使用广泛,主要适用于 web 开发领域。PHP 的文件后缀名为 php。
下面小编就为大家带来一篇深入理解 PHP 中的 empty 和 isset 函数。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
近日被问到 PHP 中 empty 和 isset 函数时怎么判断变量的,刚开始我是一脸懵逼的,因为我自己也只是一知半解,为了弄懂其真正的原理,赶紧翻开源码研究研究。经过分析可发现两个函数调用的都是同一个函数,因此本文将对两个函数一起分析。
我在 github 有对 PHP 源码更详细的注解。感兴趣的可以围观一下,给个 star。PHP5.4 源码注解。可以通过 commit 记录查看已添加的注解。
函数使用格式
empty
- bool empty(mixed $var)
判断变量是否为空。
isset
- bool isset(mixed $var[, mixed $...])
判断变量是否被设置且不为 NULL。
参数说明
对于 empty,在 PHP5.5 版本以前,empty 只支持变量参数,其他类型的参数会导致解析错误,比如函数调用的结果不能作为参数。
对于 isset,如果变量被如 unset 的函数设为 NULL,则函数会返回 false。如果多个参数被传递到 isset 函数,那么只有所有参数都被设置 isset 函数才会返回 true。从左到右计算,一旦遇到没被设置的变量就停止。
运行示例
- $result = empty(0); // true
- $result = empty(null); // true
- $result = empty(false); // true
- $result = empty(array()); // true
- $result = empty('0'); // true
- $result = empty(1); // false
- $result = empty(callback
- function); // 报错
- $a = null;
- $result = isset($a); // false;
- $a = 1;
- $result = isset($a); // true;
- $a = 1;
- $b = 2;
- $c = 3;
- $result = isset($a, $b, $c); // true
- $a = 1;
- $b = null;
- $c = 3;
- $result = isset($a, $b, $c); // false
找到函数的定义位置
实际上,empty 不是一个函数,而是一个语言结构。语言结构是在 PHP 程序运行前编译好的,因此不能像之前那样简单地搜索 "PHP_FUNCTION empty" 或 "ZEND_FUNCTION empty" 查看其源码。要想看 empty 等语言结构的源码,先要理解 PHP 代码执行的机制。
PHP 执行代码会经过 4 个步骤,其流程图如下所示:
在第一个阶段,即 Scanning 阶段,程序会扫描 zend_language_scanner.l 文件将代码文件转换成语言片段。对于 isset 和 empty 函数来说,在 zend_language_scanner.l 文件中搜索 empty 和 isset 可以得到函数在此文件中的宏定义如下:
- <ST_IN_SCRIPTING>"isset" {
- return T_ISSET;
- }
- <ST_IN_SCRIPTING>"empty" {
- return T_EMPTY;
- }
接下来就到了 Parsing 阶段,这个阶段,程序将 T_ISSET 和 T_EMPTY 等 Tokens 转换成有意义的表达式,此时会做语法分析,Tokens 的 yacc 保存在 zend_language_parser.y 文件中,可以找到 T_ISSET 和 T_EMPTY 的定义
- internal_functions_in_yacc:
- T_ISSET '(' isset_variables ')' { $$ = $3; }
- | T_EMPTY '(' variable ')' { zend_do_isset_or_isempty(ZEND_ISEMPTY, &$$, &$3 TSRMLS_CC); }
- | 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_EVAL '(' expr ')' { zend_do_include_or_eval(ZEND_EVAL, &$$, &$3 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); }
- ;
isset 和 empty 函数最终都执行了 zend_do_isset_or_isempty 函数,继续查找
grep -rn "zend_do_isset_or_isempty"
可以发现,此函数在 zend_compile.c 文件中定义。
函数执行步骤
1、解析参数
2、检查是否为可写变量
3、如果是变量的 op_type 是 IS_CV(编译时期的变量),则设置其 opcode 为 ZEND_ISSET_ISEMPTY_VAR;否则从 active_op_array 中获取下一个 op 值,根据其 op 值设置 last_op 的 opcode。
4、设置了 opcode 之后,之后会交给 zend_excute 执行。
源码解读
IS_CV 是编译器使用的一种 cache 机制,这种变量保存着它被引用的变量的地址,当一个变量第一次被引用的时候,就会被 CV 起来,以后这个变量的引用就不需要再去查找 active 符号表了。
对于 empty 函数,到了 opcode 的步骤后,参阅 opcode 处理函数,可以知道,isset 和 empty 在 excute 的时候执行的是 ZEND_ISSET_ISEMPTY_VAR 等一系列函数,以 ZEND_ISSET_ISEMPTY_VAR_SPEC_CV_VAR_HANDLER 为例,找到这个函数的定义在 zend_vm_execute.h。查看函数可以知道,empty 函数的最终执行函数是 i_zend_is_true(),而 i_zend_is_true 函数定义在 zend_execute.h。i_zend_is_true 函数的核心代码如下:
- switch (Z_TYPE_P(op)) {
- case IS_NULL:
- result = 0;
- break;
- case IS_LONG:
- case IS_BOOL:
- case IS_RESOURCE:
- // empty参数为整数时非0的话就为false
- result = (Z_LVAL_P(op) ? 1 : 0);
- break;
- case IS_DOUBLE:
- result = (Z_DVAL_P(op) ? 1 : 0);
- break;
- case IS_STRING:
- if (Z_STRLEN_P(op) == 0 || (Z_STRLEN_P(op) == 1 && Z_STRVAL_P(op)[0] == '0')) {
- // empty("0") == true
- result = 0;
- } else {
- result = 1;
- }
- break;
- case IS_ARRAY:
- // empty(array) 是根据数组的数量来判断
- result = (zend_hash_num_elements(Z_ARRVAL_P(op)) ? 1 : 0);
- break;
- case IS_OBJECT:
- if (IS_ZEND_STD_OBJECT( * op)) {
- TSRMLS_FETCH();
- if (Z_OBJ_HT_P(op) - >cast_object) {
- zval tmp;
- if (Z_OBJ_HT_P(op) - >cast_object(op, &tmp, IS_BOOL TSRMLS_CC) == SUCCESS) {
- result = Z_LVAL(tmp);
- break;
- }
- } else if (Z_OBJ_HT_P(op) - >get) {
- zval * tmp = Z_OBJ_HT_P(op) - >get(op TSRMLS_CC);
- if (Z_TYPE_P(tmp) != IS_OBJECT) {
- /* for safety - avoid loop */
- convert_to_boolean(tmp);
- result = Z_LVAL_P(tmp);
- zval_ptr_dtor( & tmp);
- break;
- }
- }
- }
- result = 1;
- break;
- default:
- result = 0;
- break;
- }
这段代码比较直观,函数没有对检测值做任何的转换,通过这段代码来进一步分析示例中的 empty 函数做分析:
empty(null),到 IS_NULL 分支,result=0,i_zend_is_true() == 0,!i_zend_is_true() == 1,因此返回 true。
empty(false),到 IS_BOOL 分支,result = ZLVAL_P(false) = 0,i_zend_is_true() == 0,!i_zend_is_true() == 1,因此返回 true。
empty(array()),到 IS_ARRAY 分支,result = zend_hash_num_elements(Z_ARRVAL_P(op)) ? 1 : 0),zend_hash_num_elements 返回数组元素的数量,array 为空,因此 result 为 0,i_zend_is_true() == 0,!i_zend_is_true() == 1,因此返回 true。
empty('0'),到 IS_STRING 分支,因为 Z_STRLENP(op) == 1 且 Z_STRVAL_P(op)[0] == '0',因此 result 为 0,i_zend_is_true() == 0,!i_zend_is_true() == 1,因此返回 true。
empty(1),到 IS_LONG 分支,result = Z_LVAL_P(op) = 1,i_zend_is_true == 1,!i_zend_is_true() == 0,因此返回 false。
对于 isset 函数,最终实现判断的代码是:
- if (isset && Z_TYPE_PP(value) != IS_NULL) {
- ZVAL_BOOL(&EX_T(opline->result.var).tmp_var, 1);
- } else {
- ZVAL_BOOL(&EX_T(opline->result.var).tmp_var, 0);
- }
只要 value 被设置了且不为 NULL,isset 函数就返回 true。
小结
这次阅读这两个函数的源码,学习到了:
1、PHP 代码在编译期间的执行步骤
2、如何查找 PHP 语言结构的源码位置
3、如何查找 opcode 处理函数的具体函数
学无止境,每个人都有自己的短板,只有通过不断学习才能将自己的短板补上。
原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。
如果本文对你有帮助,请点下推荐吧,谢谢 ^_^
以上这篇深入理解 PHP 中的 empty 和 isset 函数就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持 PHPERZ。
来源: http://www.phperz.com/article/17/0824/343585.html