看手册说define定义的常量只允许:
仅允许标量和 null。标量的类型是 integer, float,string 或者 boolean。 也能够定义常量值的类型为 resource ,但并不推荐这么做,可能会导致未知状况的发生。
今天阅读php源码,发现define的第二个参数其实也可以是一个对象。
先贴一段示例:
<span class</span><span A { </span><span public</span> <span function</span><span __toString() { </span><span return</span> 'bar'<span ; } } </span><span $a</span> = <span new</span><span A(); </span><span define</span>('foo', <span $a</span><span ); </span><span echo</span> foo;<br />// 输出bar
接着来看看php中的define究竟是如何实现的:
<span ZEND_FUNCTION(define) { </span><span char</span> *<span name; </span><span int</span><span name_len; zval </span>*<span val; zval </span>*val_free =<span NULL; zend_bool non_cs </span>= <span 0</span><span ; </span><span int</span> case_sensitive =<span CONST_CS; zend_constant c; </span><span //</span><span 接收3个参数,string,zval,bool</span> <span if</span> (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, <span "</span><span sz|b</span><span "</span>, &name, &name_len, &val, &non_cs) ==<span FAILURE) { </span><span return</span><span ; } </span><span //</span><span 是否大小写敏感</span> <span if</span><span (non_cs) { case_sensitive </span>= <span 0</span><span ; } </span><span //</span><span 如果define类常量,则报错</span> <span if</span> (zend_memnstr(name, <span "</span><span ::</span><span "</span>, <span sizeof</span>(<span "</span><span ::</span><span "</span>) - <span 1</span>, name +<span name_len)) { zend_error(E_WARNING, </span><span "</span><span Class constants cannot be defined or redefined</span><span "</span><span ); RETURN_FALSE; } </span><span //</span><span 获取真正的值,用val保存</span> <span repeat: </span><span switch</span><span (Z_TYPE_P(val)) { </span><span case</span><span IS_LONG: </span><span case</span><span IS_DOUBLE: </span><span case</span><span IS_STRING: </span><span case</span><span IS_BOOL: </span><span case</span><span IS_RESOURCE: </span><span case</span><span IS_NULL: </span><span break</span><span ; </span><span case</span><span IS_OBJECT: </span><span if</span> (!<span val_free) { </span><span if</span> (Z_OBJ_HT_P(val)-><span get</span><span ) { val_free </span>= val = Z_OBJ_HT_P(val)-><span get</span><span (val TSRMLS_CC); </span><span goto</span><span repeat; } </span><span else</span> <span if</span> (Z_OBJ_HT_P(val)-><span cast_object) { ALLOC_INIT_ZVAL(val_free); </span><span if</span> (Z_OBJ_HT_P(val)->cast_object(val, val_free, IS_STRING TSRMLS_CC) ==<span SUCCESS) { val </span>=<span val_free; </span><span break</span><span ; } } } </span><span /*</span><span no break </span><span */</span> <span default</span><span : zend_error(E_WARNING,</span><span "</span><span Constants may only evaluate to scalar values</span><span "</span><span ); </span><span if</span><span (val_free) { zval_ptr_dtor(</span>&<span val_free); } RETURN_FALSE; } </span><span //</span><span 构建常量</span> c.value = *<span val; zval_copy_ctor(</span>&<span c.value); </span><span if</span><span (val_free) { zval_ptr_dtor(</span>&<span val_free); } c.flags </span>= case_sensitive; <span /*</span><span non persistent </span><span */ // 如果大小写不敏感,则为0,敏感则为1</span><span c.name </span>=<span zend_strndup(name, name_len); c.name_len </span>= name_len+<span 1</span><span ; c.module_number </span>=<span PHP_USER_CONSTANT; <span // 标注非内核常量,而是用户定义的常量</span> </span><span //</span><span 注册常量</span> <span if</span> (zend_register_constant(&c TSRMLS_CC) ==<span SUCCESS) { RETURN_TRUE; } </span><span else</span><span { RETURN_FALSE; } }</span>
注意以repeat开始的一段循环,还用到了goto语句T_T
这段代码的作用为:
如何将object成6个类型之一呢?从代码上看有2种手段:
<span if</span> (Z_OBJ_HT_P(val)-><span get</span><span ) { val_free </span>= val = Z_OBJ_HT_P(val)-><span get</span><span (val TSRMLS_CC); </span><span goto</span><span repeat; }<br />// __toString()方法会在cast_object中被调用 </span><span else</span> <span if</span> (Z_OBJ_HT_P(val)-><span cast_object) { ALLOC_INIT_ZVAL(val_free); </span><span if</span> (Z_OBJ_HT_P(val)->cast_object(val, val_free, IS_STRING TSRMLS_CC) ==<span SUCCESS) { val </span>=<span val_free; </span><span break</span><span ; } }</span>
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 <span int</span> zend_std_cast_object_tostring(zval *readobj, zval *writeobj, <span int</span> type TSRMLS_DC) <span /*</span><span {{{ </span><span */</span><span { zval </span>*<span retval; zend_class_entry </span>*<span ce; </span><span switch</span><span (type) { </span><span case</span><span IS_STRING: ce </span>=<span Z_OBJCE_P(readobj); </span><span //</span><span 如果用户的class中定义了__toString,则尝试调用</span> <span if</span> (ce->__tostring &&<span (zend_call_method_with_0_params(</span>&readobj, ce, &ce->__tostring, <span "</span><span __tostring</span><span "</span>, &retval) ||<span EG(exception))) { …… } </span><span return</span><span FAILURE; …… } </span><span return</span><span FAILURE; }</span>
从上述具体实现来看,默认的cast_object就是去寻找class中的__tostring方法然后调用...
回到刚开始的例子,('foo',
<span define</span>('PHP_INT_MAX', 1); <span //</span><span 返回FALSE</span> <span define</span>('FOO', 1); <span //</span><span 返回TRUE</span> <span define</span>('FOO', 2); <span //</span><span 返回FALSE</span>
上面代码包含了两种情况,一是我们尝试重新定义php内核的预定义常量,比如PHP_INT_MAX,这显然会失败。第二种情况是我们曾经在代码的某个位置定义过了一个常量FOO,然后又在接下来的程序中再次定义它,这也会造成失败。因此,在编码时最好将所有需要定义的常量写在一起,以免造成name重复。
对其name不做任何要求,当然也不需要name是一个合法的php变量名。因此,我们可以让define的常量取一些稀奇古怪的名称。例如:
<span define</span>('>_<', 123); <span //</span><span 返回TRUE</span> <span echo</span> >_<; <span // </span><span syntax error</span>
不过如果定义了这样的常量,是没法直接使用的,会报语法错误。正确的使用方法如下:
<span define</span>('>_<', 123); <span //</span><span 返回TRUE</span> <span echo</span> <span constant</span>('>_<'); <span //</span><span 输出123</span>