我们先看一个php常量的定义方法
define(‘PRICE‘, 30);
之前,我一直把define和C中的宏定义理解一致,因此在使用的时候也只是将其当成简单地字符替换。后来研究了PHP内核以后,发现PHP中的常量和宏定义完全不是一回事。
在PHP脚本运行的过程中,zend引擎会维护一个常量列表,对于普通用户来说,可以对这个常量列表进行CRUD操作,api分别为
define():定义一个常量
defined(): 判断一个常量是否存在
constant(): 得到一个常量的值
我们来看下PHP内核中常量的定义
typedef struct _zend_constant{
zval value;
int flags;
char *name;
uint name_len;
int module_number;
} _zend_constant;
其中value为普通的变量结构zval,在此基础上,常量还定义了标记,常量名和模块号三个属性。
1. 标记(flags)
首先看常量的标记属性flag,目前可供选择的几个可能值分别为
用户态(可通过define方法的第三个参数赋值):
1: case sensitive
0: case insensitive
内核态:
CONST_PERSISTANT: persistent
CONST_CT_SUBST: allow compile-time substitution
用户定义的常量的标记默认为case sensitive, 也可通过define函数的第三个参数进行修改。对于内核态标记,CONST_PERSISTANT代表这个常量在内存申请的时候需要持久化。
上图是PHP脚本运行的的生命周期,我们知道多个request共享一次MINIT和MSHUTDOWN过程,而每个request有自己的RINIT和RSHUTDOWN过程,因此在MINIT中初始化的变量会常驻内存当中。这样被标记为CONST_PERSISTANT的常量只会在MSHUTDOWN中才会被析构掉。这也就不难理解,在内核C代码中,一些字符串和数字作为代码的一部分也被定义为PHP内核中的常量,它们通常就会被标记为CONST_PERSISTENT常量。
而CONST_CT_SUBST目前在内核里只有5个(TRUE, FALSE, NULL, ZEND_THREAD_SAFE, ZEND_DEBUG_BUILD),
2.模块号(module_number)
模块号同样分为用户态和内核态,确切的说,模块号就是用来做此区分的。用户定义的常量均为PHP_USER_CONSTANT, 除此以外,还有一些PHP内置的标准常量,例如E_ALL, E_WARNING等。在zend引擎启动以后,zend会进行标准常量的注册工作(zend_register_standard_constants()),一般来说,这些标准常量都被标记为持久化常量,即CONST_PERSISTENT
3. define()函数
define是PHP的内置接口,用户会通过define来定义常量,其实该方法过程如下
4. 魔术常量
PHP中还提供了一种魔术常量,他们的值是随着外部环境的变化而变化的,例如
__LINE__ 当前文件的行号
__FILE__ 文件的完整路径
这些魔术常量不是真正的常量(_zend_constants),PHP内核在此法解析的时候就会将其替换掉。
最后附上define的源码(在Zend/zend_builtin_functions.c中):
/* {{{ proto bool define(string constant_name, mixed value, boolean case_insensitive=false)
Define a new constant */
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;
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;
}
/* class constant, check if there is name and make sure class is valid & exists */
if (zend_memnstr(name, "::", sizeof("::") - 1, name + name_len)) {
zend_error(E_WARNING, "Class constants cannot be defined or redefined");
RETURN_FALSE;
}
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 = IS_INTERNED(name) ? name : zend_strndup(name, name_len);
if(c.name == NULL) {
RETURN_FALSE;
}
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;
}
}
/* }}} */