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网上调查系统
Oct 09 PHP
php 调用远程url的六种方法小结
Nov 02 PHP
PHP读取XML值的代码(推荐)
Jan 01 PHP
IIS6.0 开启Gzip方法及PHP Gzip函数分享
Jun 08 PHP
朋友网关于QQ相关的PHP代码(研究QQ的绝佳资料)
Jan 26 PHP
PHP 魔术变量和魔术函数详解
Feb 25 PHP
PHP配置把错误日志以邮件方式发送方法(Windows系统)
Jun 23 PHP
PHP实现事件机制的方法
Jul 10 PHP
php $_SESSION会员登录实例分享
Jan 19 PHP
PHP使用Redis实现防止大并发下二次写入的方法
Oct 09 PHP
tp5(thinkPHP5)框架连接数据库的方法示例
Dec 24 PHP
ThinkPHP5.1验证码功能实现的示例代码
Jun 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
自制短波长线天线频率预选器 - 成功消除B2K之流的镜像
2021/03/02 无线电
php面向对象全攻略 (二) 实例化对象 使用对象成员
2009/09/30 PHP
使用php检测用户当前使用的浏览器是否为IE浏览器
2013/12/03 PHP
PHP使用正则表达式获取微博中的话题和对象名
2015/07/18 PHP
JavaScript Event学习第五章 高级事件注册模型
2010/02/07 Javascript
extjs中grid中嵌入动态combobox的应用
2011/01/01 Javascript
jquery 插件学习(四)
2012/08/06 Javascript
基于JQuery的多标签实现代码
2012/09/19 Javascript
利用ajaxfileupload插件实现文件上传无刷新的具体方法
2013/06/08 Javascript
jquery修改网页背景颜色通过css方法实现
2014/06/06 Javascript
JavaScript学习笔记之JS函数
2015/01/22 Javascript
jQuery使用drag效果实现自由拖拽div
2015/06/11 Javascript
js数组常用操作方法小结(增加,删除,合并,分割等)
2016/08/02 Javascript
利用Three.js如何实现阴影效果实例代码
2017/09/26 Javascript
Angular实现的table表格排序功能完整示例
2017/12/22 Javascript
Angular 开发学习之Angular CLI的安装使用
2017/12/31 Javascript
vue.js2.0点击获取自己的属性和jquery方法
2018/02/23 jQuery
angular 未登录状态拦截路由跳转的方法
2018/10/09 Javascript
JavaScript 对引擎、运行时、调用堆栈的概述理解
2018/10/22 Javascript
element-ui上传一张图片后隐藏上传按钮功能
2019/05/22 Javascript
在Vue 中获取下拉框的文本及选项值操作
2020/08/13 Javascript
Python 获取新浪微博的最新公共微博实例分享
2014/07/03 Python
python遍历目录的方法小结
2016/04/28 Python
python学习必备知识汇总
2017/09/08 Python
Python读取word文本操作详解
2018/01/22 Python
python字符串string的内置方法实例详解
2018/05/14 Python
浅谈Python里面小数点精度的控制
2018/07/16 Python
Python 依赖库太多了该如何管理
2019/11/08 Python
html5跨域通讯之postMessage的用法总结
2013/11/07 HTML / CSS
英国最大的老式糖果店:A Quarter Of
2017/04/08 全球购物
Skyscanner英国:苏格兰的全球三大领先航班搜索服务之一
2017/11/09 全球购物
京东港澳售:京东直邮港澳台
2018/01/31 全球购物
大学生旅游业创业计划书
2014/01/29 职场文书
优秀驾驶员先进事迹材料
2014/05/04 职场文书
创先争优公开承诺书
2014/08/30 职场文书
python自动化操作之动态验证码、滑动验证码的降噪和识别
2021/08/30 Python