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 基于文件头的文件类型验证类函数
May 01 PHP
PHP中exec与system用法区别分析
Sep 22 PHP
简单说说PHP优化那些事(经验分享)
Nov 27 PHP
关于PHP开发的9条建议
Jul 27 PHP
PHP结合jQuery插件ajaxFileUpload实现异步上传文件实例
Aug 17 PHP
ThinkPHP使用Ueditor的方法详解
May 20 PHP
PHP使用curl函数发送Post请求的注意事项
Nov 26 PHP
CI框架实现优化文件上传及多文件上传的方法
Jan 04 PHP
PHP实现的迪科斯彻(Dijkstra)最短路径算法实例
Sep 16 PHP
关于ThinkPHP中的异常处理详解
May 11 PHP
PHP设计模式之迭代器模式Iterator实例分析【对象行为型】
Apr 26 PHP
PHP网页缓存技术优点及代码实例
Jul 29 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引用
2015/02/22 PHP
8个必备的PHP功能开发
2015/10/02 PHP
分享PHP源码批量抓取远程网页图片并保存到本地的实现方法
2015/12/01 PHP
Zend Framework入门教程之Zend_Session会话操作详解
2016/12/08 PHP
PHP 图片合成、仿微信群头像的方法示例
2019/10/25 PHP
一个对于js this关键字的问题
2007/01/09 Javascript
javaScript 简单验证代码(用户名,密码,邮箱)
2009/09/28 Javascript
node.js中的fs.chmod方法使用说明
2014/12/18 Javascript
JS与jQuery遍历Table所有单元格内容的方法
2015/12/07 Javascript
JavaScript登录验证码的实现
2016/10/27 Javascript
JS DOMReady事件的六种实现方法总结
2016/11/23 Javascript
Node.js如何实现注册邮箱激活功能 (常见)
2017/07/23 Javascript
浅谈vue项目优化之页面的按需加载(vue+webpack)
2017/12/11 Javascript
angular学习之动态创建表单的方法
2018/12/07 Javascript
在 Vue.js中优雅地使用全局事件的方法
2019/02/01 Javascript
解决element ui select下拉框不回显数据问题的解决
2019/02/20 Javascript
layui 解决form表单点击无反应的问题
2019/10/25 Javascript
[01:03]悬念揭晓 11月26日DOTA2完美盛典不见不散
2017/11/23 DOTA
利用pyinstaller或virtualenv将python程序打包详解
2017/03/22 Python
Python变量和字符串详解
2017/04/29 Python
详解python使用Nginx和uWSGI来运行Python应用
2018/01/09 Python
使用python将大量数据导出到Excel中的小技巧分享
2018/06/14 Python
Python图像处理之gif动态图的解析与合成操作详解
2018/12/30 Python
Python设计模式之策略模式实例详解
2019/01/21 Python
Python3.6实现带有简单界面的有道翻译小程序
2019/04/16 Python
python实现文字版扫雷
2020/04/24 Python
零基础学python应该从哪里入手
2020/08/11 Python
Css3+Js制作漂亮时钟(附源码)
2013/04/24 HTML / CSS
瑞典时尚服装购物网站:Miinto.se
2017/10/30 全球购物
皮姆斯勒语言学习:Pimsleur Language Programs
2018/06/30 全球购物
日本航空官方网站:JAL
2019/06/19 全球购物
班级口号大全
2014/06/09 职场文书
高中生打架检讨书1000字
2015/02/17 职场文书
百年孤独读书笔记
2015/06/29 职场文书
MySQL聚簇索引和非聚簇索引的区别详情
2022/06/14 MySQL
MySQL数据库之存储过程 procedure
2022/06/16 MySQL