在鸟哥《深入理解PHP原理之对象(一)》看到一段挺有意思的代码

“PHP通过这种比较ugly但是简单高效的方法, 实现了对属性访问权限的标识.知道了, 我们就可以干一些不合常理的事请, 比如访问对象的私有/保护属性”

<?php
class Foo {
    private $_name = "laruence";
    protected $_age = 28;
}
$foo = new Foo();
$arr = (array) $foo;
var_dump($arr["\0Foo\0_name"]);
var_dump($arr["\0*\0_age"]);
//output:
string(8) "laruence"
int(28)

至于这算不算BUG本文就不议论了,有兴趣的可以看看这里 (见: Bug #44273 access to private and protected class variables allowed when casting to array ): 下面有相关议论

那为什么对象转成数组以后能通过构建特殊的key去访问呢?直接看关键代码吧! (已下代码PHP版本为: 5.4.27)

/* Zend/zend_API.c +3361*/
ZEND_API int zend_declare_property_ex(zend_class_entry *ce, const char *name, int name_length, zval *property, int access_type, const char *doc_comment, int doc_comment_len TSRMLS_DC)
{
.....
    switch (access_type & ZEND_ACC_PPP_MASK) {
        case ZEND_ACC_PRIVATE: {
                char *priv_name;
                int priv_name_length;

                zend_mangle_property_name(&priv_name, &priv_name_length, ce->name, ce->name_length, name, name_length, ce->type & ZEND_INTERNAL_CLASS);
                property_info.name = priv_name;
                property_info.name_length = priv_name_length;
            }
            break;
        case ZEND_ACC_PROTECTED: {
                char *prot_name;
                int prot_name_length;

                zend_mangle_property_name(&prot_name, &prot_name_length, "*", 1, name, name_length, ce->type & ZEND_INTERNAL_CLASS);
                property_info.name = prot_name;
                property_info.name_length = prot_name_length;
            }
            break;
        case ZEND_ACC_PUBLIC:
            if (IS_INTERNED(name)) {
                property_info.name = (char*)name;
            } else {
                property_info.name = ce->type & ZEND_INTERNAL_CLASS ? zend_strndup(name, name_length) : estrndup(name, name_length);
            }
            property_info.name_length = name_length;
            break;
    }
.....
	zend_hash_quick_update(&ce->properties_info, name, name_length+1, h, &property_info, sizeof(zend_property_info), NULL);

	return SUCCESS;
}

可以看到当属性是private和protected时候调用zend_mangle_property_name来构造property_info.name (转换后的key),而它们第3,4个参数也不一样,private传入的是类名,而protected传入的是*字符。在看看zend_mangle_property_name函数的实现就一目了然了。

/* Zend/zend_compile.c +5037 */
ZEND_API void zend_mangle_property_name(char **dest, int *dest_length, const char *src1, int src1_length, const char *src2, int src2_length, int internal)
{
	char *prop_name;
	int prop_name_length;

	prop_name_length = 1 + src1_length + 1 + src2_length;
	prop_name = pemalloc(prop_name_length + 1, internal);
	prop_name[0] = '\0';
	memcpy(prop_name + 1, src1, src1_length+1);
	memcpy(prop_name + 1 + src1_length + 1, src2, src2_length+1);

	*dest = prop_name;
	*dest_length = prop_name_length;
}

所以对象的private属性我们可以强制转换为array后通过$obj["\0类名\0属性名"]来访问,而protected则是$obj["\0*\0属性名"]

至于流程可以通过zend_do_declare_property关键字到 http://lxr.php.net/ 搜索, 大概就是

  • zend_do_declare_property
  • zend_declare_property_ex
  • zend_mangle_property_name

嗯,好像也没写什么的样子,一些细节比如property_info被zend_hash_quick_update, 类型的转换都没理解清楚。

PHP __compare 魔术方法的实现

__compare 魔术方法的实现 Continue reading

PHP 实用的两个array函数

Published on May 12, 2014

暂时就这样吧!

Published on May 09, 2014