第一组问题:
1.成员变量与静态成员变量可否同名;
2.类的成员变量和静态成员变量交换访问方式能否访问到。
第二组问题:
1.成员方法与静态成员方法能否同名;
2.成员方法或静态成员方法是否可以与成员变量或静态成员变量同名;
3.如果交换访问方式是否可以成功访问。
实验准备:
class Test{ public static $sv = 1; public $pv = 2; public static function sv(){ echo 'static function sv.<br>'; } public function pv(){ echo 'normal function pv.<br>'; } }
第一组问题实验:
echo Test::$sv;//1 echo Test::$pv;//Fatal error: Uncaught Error: Access to undeclared static property: Test::$pv $test = new Test(); echo $test->sv;//空白 var_dump($test->sv);//NULL
1.成员变量与静态成员变量不能同名;
2.交换访问方式不能访问到。
第二组问题实验:
Test::sv();//static function sv. Test::pv();//normal function pv. $test = new Test(); $test->sv();//static function sv. $test->pv();//normal function pv.
1.成员方法与静态成员方法不能同名;
2.成员方法/静态成员方法可以与成员变量/静态成员变量同名;
3.交换访问方式可以访问得到,但不推荐交换调用。
从类的存储结构出发理解以上问题:
//Zend\zend.h:116 struct _zend_class_entry { //......省略部分内容 int default_properties_count; int default_static_members_count; zval *default_properties_table; zval *default_static_members_table; zval *static_members_table; HashTable function_table; HashTable properties_info; HashTable constants_table; //......又省略大部分内容 };
1. 类的成员变量数组(zval *default_properties_table)和静态成员变量数组(zval *default_static_members_table)虽然是类结构体的不同属性,但属性的访问均通过(HashTable properties_info)来查找(参见大牛分享的:PHP7类结构分析)。简单来说就是:
静态成员变量:根据属性名在properties_info中找到属性在静态变量数组中的下标,再根据下标去类结构的default_static_members_table中取值,静态变量的操作在类空间内,各实例对象共享;
普通成员变量:根据属性名在properties_info中找到属性在对象空间的偏移(普通成员变量的操作对应于类的实例对象,各个对象有自己独立的空间),根据该偏移取到对象普通成员变量值。
(1)不能同名是因为均需通过属性名在同一个HashTable中查找;
(2)不能交换访问是因为:
静态变量访问会执行zend_object_handlers.c中的zend_std_get_static_property方法,通过属性名在类的properties_info中找到对应的zend_property_info,通过其flags判断访问的属性类型是否为静态,若不是静态的会抛出我们看到的异常。
普通成员变量访问会执行zend_object_handlers.c中的zend_std_read_property方法,在其中调用zend_get_property_offset方法时判断了要访问的属性如果时静态的,则抛出E_NOTICE级别的错误,再后面的处理还没看懂。
2. 成员方法与静态成员方法存于类结构体的同一属性(HashTable function_table)中,所以不能重名;
可以交换访问方式的原因:
(1)调用静态成员方法会执行zend_object_handlers.c中的zend_std_get_static_method方法,从类的function_table中查找到对应方法后会判断如果方法不是静态的将抛出异常,前提是满足预处理指令#if MBO_0,它的含义暂不清楚,猜测和php设置的错误级别有关,经测试,将error_reporting设置为E_ALL时,将抛出如下错误:
Deprecated: Non-static method Test::pv() should not be called statically
(2)用普通成员方法调用会执行zend_object_handlers.c中的zend_std_get_method方法,其中并未特别处理静态方法的情况,所以可以访问到静态方法。