php define的第二个参数使用方法


Posted in PHP onNovember 04, 2013

看手册说define定义的常量只允许:
仅允许标量和 null。标量的类型是 integer, float,string 或者 boolean。 也能够定义常量值的类型为 resource ,但并不推荐这么做,可能会导致未知状况的发生。
今天阅读php源码,发现define的第二个参数其实也可以是一个对象。
先贴一段示例:

class A {
    public function __toString() {
        return 'bar';
    }
}$a = new A();
define('foo', $a);
echo foo;
// 输出bar

接着来看看php中的define究竟是如何实现的:
ZEND_FUNCTION(define)
{
    char *name;
    int name_len;
    zval *val;
    zval *val_free = NULL;
    zend_bool non_cs = 0;
    int case_sensitive = CONST_CS;
    zend_constant c;    // 接收3个参数,string,zval,bool
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|b", &name, &name_len, &val, &non_cs) == FAILURE) {
        return;
    }
    // 是否大小写敏感
    if(non_cs) {
        case_sensitive = 0;
    }
    // 如果define类常量,则报错
    if (zend_memnstr(name, "::", sizeof("::") - 1, name + name_len)) {
        zend_error(E_WARNING, "Class constants cannot be defined or redefined");
        RETURN_FALSE;
    }
    // 获取真正的值,用val保存
repeat:
    switch (Z_TYPE_P(val)) {
        case IS_LONG:
        case IS_DOUBLE:
        case IS_STRING:
        case IS_BOOL:
        case IS_RESOURCE:
        case IS_NULL:
            break;
        case IS_OBJECT:
            if (!val_free) {
                if (Z_OBJ_HT_P(val)->get) {
                    val_free = val = Z_OBJ_HT_P(val)->get(val TSRMLS_CC);
                    goto repeat;
                } else if (Z_OBJ_HT_P(val)->cast_object) {
                    ALLOC_INIT_ZVAL(val_free);
                    if (Z_OBJ_HT_P(val)->cast_object(val, val_free, IS_STRING TSRMLS_CC) == SUCCESS) {
                        val = val_free;
                        break;
                    }
                }
            }
            /* no break */
        default:
            zend_error(E_WARNING,"Constants may only evaluate to scalar values");
            if (val_free) {
                zval_ptr_dtor(&val_free);
            }
            RETURN_FALSE;
    }
    // 构建常量
    c.value = *val;
    zval_copy_ctor(&c.value);
    if (val_free) {
        zval_ptr_dtor(&val_free);
    }
    c.flags = case_sensitive; /* non persistent */
    c.name = zend_strndup(name, name_len);
    c.name_len = name_len+1;
    c.module_number = PHP_USER_CONSTANT;
    // 注册常量
    if (zend_register_constant(&c TSRMLS_CC) == SUCCESS) {
        RETURN_TRUE;
    } else {
        RETURN_FALSE;
    }
}

注意以repeat开始的一段循环,还用到了goto语句T_T
这段代码的作用为:
对于int,float,string,bool,resource,null,则实际定义的常量时直接使用这些值
对于object,则需要将object转成上述6个类型之一(如果转型之后依然是object,则继续转型)
如何将object成6个类型之一呢?从代码上看有2种手段:
if (Z_OBJ_HT_P(val)->get) {
    val_free = val = Z_OBJ_HT_P(val)->get(val TSRMLS_CC);
    goto repeat;
}
// __toString()方法会在cast_object中被调用
else if (Z_OBJ_HT_P(val)->cast_object) {
    ALLOC_INIT_ZVAL(val_free);
    if (Z_OBJ_HT_P(val)->cast_object(val, val_free, IS_STRING TSRMLS_CC) == SUCCESS)
    {
        val = val_free;
        break;
    }
}

1,Z_OBJ_HT_P(val)->get ,宏展开之后为(*val).value.obj.handlers->get
2,Z_OBJ_HT_P(val)->cast_object,宏展开之后为(*val).value.obj.handlers->cast_object
handlers是一个包含很多函数指针的结构体,具体定义参见_zend_object_handlers 。该结构体中的函数指针均用于操作object,比如读取/修改对象属性、获取/调用对象方法等等…get和cast_object也是其中之一。
对于一般的对象,php提供了标准的cast_object函数zend_std_cast_object_tostring,代码位于php-src/zend/zend-object-handlers.c中:
ZEND_API int zend_std_cast_object_tostring(zval *readobj, zval *writeobj, int type TSRMLS_DC) /* {{{ */
{
    zval *retval;
    zend_class_entry *ce;    switch (type) {
        case IS_STRING:
            ce = Z_OBJCE_P(readobj);
            // 如果用户的class中定义了__toString,则尝试调用
            if (ce->__tostring &&
                (zend_call_method_with_0_params(&readobj, ce, &ce->__tostring, "__tostring", &retval) || EG(exception))) {
                ……
            }
            return FAILURE;
        ……
    }
    return FAILURE;
}

从上述具体实现来看,默认的cast_object就是去寻找class中的__tostring方法然后调用…
回到刚开始的例子,define(‘foo', $a) ,由于$a是A的实例,并且class A中定义了__toString,因此实际上foo常量就等于toString的返回值bar。
PHP 相关文章推荐
表单复选框向PHP传输数据的代码
Nov 13 PHP
修改Zend引擎实现PHP源码加密的原理及实践
Apr 14 PHP
php设计模式 Composite (组合模式)
Jun 26 PHP
PHP导出EXCEL快速开发指南--PHPEXCEL的使用详解
Jun 03 PHP
thinkphp控制器调度使用示例
Feb 24 PHP
PHP date()函数警告: It is not safe to rely on the system解决方法
Aug 20 PHP
PHP获取一段文本显示点阵宽度和高度的方法
Mar 12 PHP
Yii2表单事件之Ajax提交实现方法
May 04 PHP
PHP实现的简单对称加密与解密方法实例小结
Aug 28 PHP
Laravel框架实现多数据库连接操作详解
Jul 12 PHP
解决tp5在nginx下修改配置访问的问题
Oct 16 PHP
laravel添加前台跳转成功页面示例
Oct 22 PHP
Linux编译升级php的详细方法
Nov 04 #PHP
php获取操作系统语言代码
Nov 04 #PHP
Php header()函数语法及使用代码
Nov 04 #PHP
php配置php-fpm启动参数及配置详解
Nov 04 #PHP
mac下安装nginx和php
Nov 04 #PHP
php使用curl模拟登录后采集页面的例子
Nov 04 #PHP
在PHP上显示JFreechart画的统计图方法
Nov 03 #PHP
You might like
PHP Memcached应用实现代码
2010/02/08 PHP
PHP中提问频率最高的11个面试题和答案
2014/09/02 PHP
PHP中大于2038年时间戳的问题处理方案
2015/03/03 PHP
PHP7多线程搭建教程
2017/04/21 PHP
yii2.0整合阿里云oss删除单个文件的方法
2017/09/19 PHP
PHP微信H5支付开发实例
2018/07/25 PHP
汉化英文版的Dreamweaver CS5并自动提示jquery
2010/11/25 Javascript
javascript中创建对象的几种方法总结
2013/11/01 Javascript
js的alert样式如何更改如背景颜色
2014/01/22 Javascript
js代码实现无缝滚动(文字和图片)
2015/08/20 Javascript
Javascript实现苹果悬浮虚拟按钮
2016/04/10 Javascript
node.js中cluster的使用教程
2017/06/09 Javascript
js中document.write和document.writeln的区别
2018/03/11 Javascript
浅谈express.js框架中间件(middleware)
2019/04/07 Javascript
详解Vue前端生产环境发布配置实战篇
2019/05/07 Javascript
Node 代理访问的实现
2019/09/19 Javascript
详解element-ui级联菜单(城市三级联动菜单)和回显问题
2019/10/02 Javascript
[06:11]2014DOTA2国际邀请赛 专访团结一心的VG战队
2014/07/21 DOTA
Python模拟登录12306的方法
2014/12/30 Python
python实现基于两张图片生成圆角图标效果的方法
2015/03/26 Python
Pandas中Series和DataFrame的索引实现
2019/06/27 Python
python查询MySQL将数据写入Excel
2020/10/29 Python
python中scrapy处理项目数据的实例分析
2020/11/22 Python
使用Html5中的cavas画一面国旗
2019/09/25 HTML / CSS
Ratchet 模态框的实现
2020/08/19 HTML / CSS
全球最大的游戏市场:G2A
2018/07/05 全球购物
美国迪克体育用品商店:DICK’S Sporting Goods
2018/07/24 全球购物
加拿大鞋网:Globo Shoes
2019/12/26 全球购物
澳大利亚美容产品及化妆品在线:Activeskin
2020/06/03 全球购物
副处级干部考察材料
2014/05/17 职场文书
国际语言毕业生求职信
2014/07/08 职场文书
捐款感谢信
2015/01/20 职场文书
2015年学校心理健康教育工作总结
2015/05/11 职场文书
会议营销主持词
2015/07/03 职场文书
干货分享:推荐信写作技巧!
2019/06/21 职场文书
html+css实现赛博朋克风格按钮
2021/05/26 HTML / CSS