php的true和TRUE引发的一些思考
最近在写php代码的时候突然很纠结一个问题,就是php里面的那些预定义常量也预定义变量到底有什么区别。比如说true
和TRUE
,
false
和FALSE
,null
和NULL
...
那么这些变量对到底有什么区别呢,作为一名热衷技术热衷死磕的码农,果断要测试一下。
var_dump(true === TRUE);
结果为bool(true)
,那意味着他们是没有区别的。那么问题来了,php为什么要放2个一毛一样的预定义变量呢?是有什么用意呢,还是本身语言设计的历史遗留问题?百度google一大圈
没有找到合适的答案,于是果断去看php的编译源代码,php的整个编译程序只有一个文件,都在zend/zend_compile.c
里面,大概7000多行,看着眼花,没办法,也要慢慢看,结果还是找到了想要的答案,这里记录过程,以便查阅。
这里看看php字面量常量的编译
static int zend_add_const_name_literal(zend_op_array *op_array, zend_string *name, zend_bool unqualified)
{
zend_string *tmp_name;
int ret = zend_add_literal_string(op_array, &name);
size_t ns_len = 0, after_ns_len = ZSTR_LEN(name);
const char *after_ns = zend_memrchr(ZSTR_VAL(name), '\\', ZSTR_LEN(name));
if (after_ns) {
after_ns += 1;
ns_len = after_ns - ZSTR_VAL(name) - 1;
after_ns_len = ZSTR_LEN(name) - ns_len - 1;
/* lowercased namespace name & original constant name */
tmp_name = zend_string_init(ZSTR_VAL(name), ZSTR_LEN(name), 0);
zend_str_tolower(ZSTR_VAL(tmp_name), ns_len);
zend_add_literal_string(op_array, &tmp_name);
/* lowercased namespace name & lowercased constant name */
tmp_name = zend_string_tolower(name);
zend_add_literal_string(op_array, &tmp_name);
if (!unqualified) {
return ret;
}
} else {
after_ns = ZSTR_VAL(name);
}
/* original unqualified constant name */
tmp_name = zend_string_init(after_ns, after_ns_len, 0);
zend_add_literal_string(op_array, &tmp_name);
/* lowercased unqualified constant name */
tmp_name = zend_string_alloc(after_ns_len, 0);
zend_str_tolower_copy(ZSTR_VAL(tmp_name), after_ns, after_ns_len);
zend_add_literal_string(op_array, &tmp_name);
return ret;
}
看到其中一个片段
/* lowercased namespace name & lowercased constant name */
tmp_name = zend_string_tolower(name);
zend_add_literal_string(op_array, &tmp_name);
tmp_name = zend_string_tolower(name)
,这里全部转换成小写了
接着往下看,发现php的好多地方都是不区分大小写的,包括IF和if, 函数名,方法名...都是不区分大小写的
void zend_begin_method_decl(zend_op_array *op_array, zend_string *name, zend_bool has_body) {
op_array->scope = ce;
op_array->function_name = zend_string_copy(name);
lcname = zend_string_tolower(name);
lcname = zend_new_interned_string(lcname);
....
方法名称也全部在编译的时候转换成小写了!
static void zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_ast_decl *decl) {
unqualified_name = decl->name;
op_array->function_name = name = zend_prefix_with_ns(unqualified_name);
lcname = zend_string_tolower(name);
...
函数名称在编译的时候也全部转换成小写了!
key = zend_build_runtime_definition_key(lcname, decl->lex_pos);
zend_hash_update_ptr(CG(function_table), key, op_array);
这里表示把编译好的函数通过key,op_array代码送入进CG(function_table)的全局函数符号表!
void zend_compile_class_decl(zend_ast *ast) {
name = zend_new_interned_string(name);
lcname = zend_string_tolower(name);
name = zend_generate_anon_class_name(decl->lex_pos);
lcname = zend_string_tolower(name);
...
编译类的定义,也全部把类名称转换成小写了,所以类的名字大小写也没关系!
继续往下看...
void zend_compile_use(zend_ast *ast)
{
zend_ast_list *list = zend_ast_get_list(ast);
uint32_t i;
zend_string *current_ns = FC(current_namespace);
uint32_t type = ast->attr;
HashTable *current_import = zend_get_import_ht(type);
zend_bool case_sensitive = type == T_CONST;
...
if (case_sensitive) {
lookup_name = zend_string_copy(new_name);
} else {
lookup_name = zend_string_tolower(new_name);
}
...
这段代码可以得出2个结论:
- use 语法是不区分大小写的
- define常量是严格区分大小写的
继续看...
if (type == T_CLASS && zend_is_reserved_class_name(new_name)) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use %s as %s because '%s' "
"is a special class name", ZSTR_VAL(old_name), ZSTR_VAL(new_name), ZSTR_VAL(new_name));
}
类名字定义,不能是预定义类名字的限制逻辑!
reserved_class_name
的定义如下:
static const struct reserved_class_name reserved_class_names[] = {
{ZEND_STRL("bool")},
{ZEND_STRL("false")},
{ZEND_STRL("float")},
{ZEND_STRL("int")},
{ZEND_STRL("null")},
{ZEND_STRL("parent")},
{ZEND_STRL("self")},
{ZEND_STRL("static")},
{ZEND_STRL("string")},
{ZEND_STRL("true")},
{ZEND_STRL("void")},
{ZEND_STRL("iterable")},
{NULL, 0}
};
所以,类名都不能是,上述预定义字符串!
接下来看看类的编译
static int zend_add_class_name_literal(zend_op_array *op_array, zend_string *name) {
{
/* Original name */
int ret = zend_add_literal_string(op_array, &name);
/* Lowercased name */
zend_string *lc_name = zend_string_tolower(name);
zend_add_literal_string(op_array, &lc_name);
zend_alloc_cache_slot(ret);
return ret;
}
void zend_begin_method_decl(zend_op_array *op_array, zend_string *name, zend_bool has_body)
{
zend_class_entry *ce = CG(active_class_entry);
zend_bool in_interface = (ce->ce_flags & ZEND_ACC_INTERFACE) != 0;
zend_bool in_trait = (ce->ce_flags & ZEND_ACC_TRAIT) != 0;
zend_bool is_public = (op_array->fn_flags & ZEND_ACC_PUBLIC) != 0;
zend_bool is_static = (op_array->fn_flags & ZEND_ACC_STATIC) != 0;
zend_string *lcname;
if (in_interface) {
if (!is_public || (op_array->fn_flags & (ZEND_ACC_FINAL|ZEND_ACC_ABSTRACT))) {
zend_error_noreturn(E_COMPILE_ERROR, "Access type for interface method "
"%s::%s() must be omitted", ZSTR_VAL(ce->name), ZSTR_VAL(name));
}
op_array->fn_flags |= ZEND_ACC_ABSTRACT;
}
if (op_array->fn_flags & ZEND_ACC_ABSTRACT) {
if (op_array->fn_flags & ZEND_ACC_PRIVATE) {
zend_error_noreturn(E_COMPILE_ERROR, "%s function %s::%s() cannot be declared private",
in_interface ? "Interface" : "Abstract", ZSTR_VAL(ce->name), ZSTR_VAL(name));
}
if (has_body) {
zend_error_noreturn(E_COMPILE_ERROR, "%s function %s::%s() cannot contain body",
in_interface ? "Interface" : "Abstract", ZSTR_VAL(ce->name), ZSTR_VAL(name));
}
ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS;
} else if (!has_body) {
zend_error_noreturn(E_COMPILE_ERROR, "Non-abstract method %s::%s() must contain body",
ZSTR_VAL(ce->name), ZSTR_VAL(name));
}
op_array->scope = ce;
op_array->function_name = zend_string_copy(name);
lcname = zend_string_tolower(name);
lcname = zend_new_interned_string(lcname);
这段代码有意思,php的什么和抽象类的修饰权限,全是在编译的时候检测的,而且,检测与否其实就是一个if
if (in_interface) {
if (!is_public || (op_array->fn_flags & (ZEND_ACC_FINAL|ZEND_ACC_ABSTRACT))) {
zend_error_noreturn(E_COMPILE_ERROR, "Access type for interface method "
"%s::%s() must be omitted", ZSTR_VAL(ce->name), ZSTR_VAL(name));
}
op_array->fn_flags |= ZEND_ACC_ABSTRACT;
}
如果是接口,但是修饰flag不是public
就报错了!
这也可以说明在php里面,写个interface
,然后写很多实现,这种模式不是很有必要,也就是欺骗一下编译器,运行的时候还是到CG(function_table)
或者CG(active_class_entry)
中取查找,会让编译过程变慢,所以特么opcache
这么有用的很大原因之一!
最后献上PHP之父Rasmus
在php巴黎会议的一段话
Rasmus in a PHP conference in Paris saying more or less: "I'm definitely not a good programmer, in terms of following strict coding rules or standards,
but I can say that if you rely on case sensitivity to recognize one function name from another, you're in kind of serious trouble
这里简单翻译一下:“我绝对不是一个好的程序员,至少在遵循严格的编码规则和标准方面, 但是我不得不说,如果你要依靠大小写来区分自己写的另一个函数,那你将会非常麻烦” 到此为止,这估计是php不区分大小写的最好的理由了。
如果您觉得本文对您有用,可以请作者喝杯咖啡。 如需商务合作请加微信(点击右边链接扫码): RockYang
版权申明 : 本站博文如非注明转载则均属作者原创文章,引用或转载请注明出处,如要商用请联系作者,谢谢。