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 相关文章推荐
玩转虚拟域名◎+ .
Oct 09 PHP
IIS+PHP+MySQL+Zend配置 (视频教程)
Dec 13 PHP
php获得当前的脚本网址
Dec 10 PHP
php 执行系统命令的方法
Jul 07 PHP
PHP 字符截取 解决中文的截取问题,不用mb系列
Sep 29 PHP
PHP采集腾讯微博的实现代码
Jan 19 PHP
PHP生成sitemap.xml地图函数
Nov 13 PHP
PHP+jQuery 注册模块的改进(一):验证码存入SESSION
Oct 14 PHP
php实现屏蔽掉黑帽SEO的搜索关键字
Apr 15 PHP
基于php(Thinkphp)+jquery 实现ajax多选反选不选删除数据功能
Feb 24 PHP
PHP调用Mailgun发送邮件的方法
May 04 PHP
PHP 7.4中使用预加载的方法详解
Jul 08 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生成自适应大小的缩略图类及使用方法分享
2014/05/06 PHP
ThinkPHP、ZF2、Yaf、Laravel框架路由大比拼
2015/03/25 PHP
解决laravel 表单提交-POST 异常的问题
2019/10/15 PHP
PHP 命名空间和自动加载原理与用法实例分析
2020/04/29 PHP
利用JQuery的load函数动态加载其它页面的内容的实现代码
2010/12/14 Javascript
javascript 上下banner替换具体实现
2013/11/14 Javascript
jQuery之过滤元素操作小结
2013/11/30 Javascript
javascript中传统事件与现代事件
2015/06/23 Javascript
JS函数定义方式的区别介绍
2016/03/22 Javascript
node.js插件nodeclipse安装图文教程
2020/10/19 Javascript
再谈javascript常见错误及解决方法
2016/09/16 Javascript
详解AngularJs中$resource和restfu服务端数据交互
2016/09/21 Javascript
javascript深拷贝的原理与实现方法分析
2017/04/10 Javascript
JS解析url查询参数的简单代码
2017/08/06 Javascript
bing Map 在vue项目中的使用详解
2018/04/09 Javascript
详解如何在vue项目中使用lodop打印插件
2018/09/27 Javascript
angularJs中orderBy筛选以及filter过滤数据的方法
2018/09/30 Javascript
微信小程序实现卡片左右滑动效果的示例代码
2019/05/01 Javascript
微信小程序云开发之数据库操作
2019/05/18 Javascript
layui 阻止图片上传的实例(before方法)
2019/09/26 Javascript
JS写滑稽笑脸运动效果
2020/05/28 Javascript
scrapy-redis源码分析之发送POST请求详解
2019/05/15 Python
python实现切割url得到域名、协议、主机名等各个字段的例子
2019/07/25 Python
基于Django快速集成Echarts代码示例
2020/12/01 Python
详解css3 Transition属性(平滑过渡菜单栏案例)
2017/09/05 HTML / CSS
JavaScript获取当前url根目录(路径)
2014/02/19 面试题
大专生自荐信
2013/10/04 职场文书
通信生自我鉴定
2014/01/18 职场文书
解除财产保全担保书
2014/05/20 职场文书
先进员工获奖感言
2014/08/14 职场文书
学生上课看漫画的检讨书
2014/09/26 职场文书
办理信用卡工作证明
2014/09/30 职场文书
预备党员转正材料
2014/12/19 职场文书
2015年度物流工作总结
2015/04/30 职场文书
2016暑期社会实践新闻稿
2015/11/25 职场文书
2016猴年开门红标语口号
2015/12/26 职场文书