代码:
<?php
class Example{
private $p1;
private $p2;
function __construct($a,$b){
$this->p1=$a;
$this->p2=$b;
}
function __get($elmname){
echo "调用get啦!"; //为了跟踪get函数的调用,所以才输出!
if($elmname=="p2")
return "保密";
return $this->$elmname;
}
function __set($elmname,$value){
if($elmname=="p1"&&$value>100)
echo "error!"."<br/>";
else
$this->$elmname=$value;
}
function __isset($name){
return isset($this->$name);
}
function __unset($name){
unset($this->$name);
}
function __destruct(){
echo "<br/>已经被摧毁!<br/>";
}
function getp2(){
return $this->p2;
}
}
$example=new Example("v1","v2");
$example->p2="vs";//相当于调用了__set函数把$example的私有属性p2 设置为"vs".echo $example->getp2()."<br/>";//所以用getp2()方法获得p2的值是vs
var_dump(isset($example->p2));//此时p2已被设置为vs所以输出bool(true)
unset($example->p2);//此时unset一下$example的私有属性p2
var_dump(isset($example->p2));//因为已经被unset了所以输出bool(false)//以上部分暂时一切正常,但是因为已经用了unset,所以后面出现了一些我不能理解的事情
echo $example->getp2()."<br/>";//此时p2已经被unset了,所以没有值了,按理说应该不输出,可是神奇的一幕发生了,此时他竟然自动调用了__get函数,输出保密!
echo $example->p2;//于是我亲自调用了__get()函数,发现正常!
unset($example->p1);//然后我把p1也unset掉了!
var_dump(isset($example->p1));//所以这句也输出保密bool(false)
echo $example->p1;//神奇的一幕又发生了!同样是被unset掉的,这次我亲自调用的__get函数却执行了两遍
?>————————————————————————输出的结果是:
vs
bool(true) bool(false) 调用get啦!保密
调用get啦!保密bool(false) 调用get啦!调用get啦!
已经被摧毁!
————————————————————————就是注释中提到的问题:关于__unset一个私有变量后,通过该类的函数访问该私有变量时发生的自动调用__get()函数的现象,和有时候调用__get()函数时却执行两次的现象!!神奇啊!!
谁能帮我解释一下为什么会出现这些现象啊?为什么__unset一个私有变量后,再访问该变量的时候,__get()函数就会不正常的执行啊?能告诉我这里面具体的调用机制么?
<?php
class Example{
private $p1;
private $p2;
function __construct($a,$b){
$this->p1=$a;
$this->p2=$b;
}
function __get($elmname){
echo "调用get啦!"; //为了跟踪get函数的调用,所以才输出!
if($elmname=="p2")
return "保密";
return $this->$elmname;
}
function __set($elmname,$value){
if($elmname=="p1"&&$value>100)
echo "error!"."<br/>";
else
$this->$elmname=$value;
}
function __isset($name){
return isset($this->$name);
}
function __unset($name){
unset($this->$name);
}
function __destruct(){
echo "<br/>已经被摧毁!<br/>";
}
function getp2(){
return $this->p2;
}
}
$example=new Example("v1","v2");
$example->p2="vs";//相当于调用了__set函数把$example的私有属性p2 设置为"vs".echo $example->getp2()."<br/>";//所以用getp2()方法获得p2的值是vs
var_dump(isset($example->p2));//此时p2已被设置为vs所以输出bool(true)
unset($example->p2);//此时unset一下$example的私有属性p2
var_dump(isset($example->p2));//因为已经被unset了所以输出bool(false)//以上部分暂时一切正常,但是因为已经用了unset,所以后面出现了一些我不能理解的事情
echo $example->getp2()."<br/>";//此时p2已经被unset了,所以没有值了,按理说应该不输出,可是神奇的一幕发生了,此时他竟然自动调用了__get函数,输出保密!
echo $example->p2;//于是我亲自调用了__get()函数,发现正常!
unset($example->p1);//然后我把p1也unset掉了!
var_dump(isset($example->p1));//所以这句也输出保密bool(false)
echo $example->p1;//神奇的一幕又发生了!同样是被unset掉的,这次我亲自调用的__get函数却执行了两遍
?>————————————————————————输出的结果是:
vs
bool(true) bool(false) 调用get啦!保密
调用get啦!保密bool(false) 调用get啦!调用get啦!
已经被摧毁!
————————————————————————就是注释中提到的问题:关于__unset一个私有变量后,通过该类的函数访问该私有变量时发生的自动调用__get()函数的现象,和有时候调用__get()函数时却执行两次的现象!!神奇啊!!
谁能帮我解释一下为什么会出现这些现象啊?为什么__unset一个私有变量后,再访问该变量的时候,__get()函数就会不正常的执行啊?能告诉我这里面具体的调用机制么?
你遇到的正是这个,没有什么不可理解的
而之前也是读取unset过的$example->p2属性,却只调用过一次!
估计是你自己没有弄清楚的原因,建议在你调试输出时加上行号 __LINE__
——————————————————————
vs
bool(true) bool(false) 调用get啦!保密
调用get啦!保密bool(false) 调用get啦!调用get啦!
已经被摧毁!
——————————————————————
他最后在bool(false)后输出了两遍 调用get啦!调用get啦!
36vs
bool(true) bool(false) 调用get啦!40保密
调用get啦!41保密bool(false) 调用get啦!调用get啦!44
已经被摧毁!
最后一个44行虽然输出了两次 调用get啦!调用get啦! 但是却只echo了一次!
这是问什么?
因为这句:
if($elmname=="p2")
return "保密";
在p2的时候直接从这句return了
-------------------------------
p1的时候call了__get两次的确有点奇怪。楼主的code太多了,我简化了一下:<?php
class Ex1{
private $var; function __construct(){
unset($this->var);
} function __get($elmname){
echo "call __get\n";
return $this->$elmname;
}}class Ex2{ function __construct(){
} function __get($elmname){
echo "call __get\n";
return $this->$elmname;
}}$ex1=new Ex1();
echo $ex1->var;$ex2=new Ex2();
echo $ex2->var;
输出大致是:
call __get
call __get
PHP Notice: Undefined property: Ex1::$var in test.php on line 11
call __get
PHP Notice: Undefined property: Ex2::$var in test.php on line 23第二个是常规的,大家都明白
第一个Ex1在构建时立刻把var unset了,这个时候实际上已经没有var,
但是call __get执行了两遍,似乎php在第二次进入__get时才意识到var是一个不存在的property
这个涉及到unset到底发生了什么。。建议楼主发到https://bugs.php.net/问问,附上代码和疑问,
就算不是真正的bug,也会有人解释给你的谢谢,如果你比较忙不打算发过去,麻烦这里说一声,我会发上去问问。
我不知道应该选那个Package affected,而且我的php版本是5.2的不知道高版本的有没有变化!所以不知道该怎么问!
代码我重写了
<?php
class Example{
private $p1;
private $p2;
function __construct($a){
$this->p1=$a;
}
function __get($elmname){
echo "Call_get()!"; //In order to follow the method __get()
return $this->$elmname;
}
function __isset($name){
return isset($this->$name);
}
function __unset($name){
unset($this->$name);
}
}
$example=new Example("v1","v2");
echo $example->p1."<br/>";
var_dump(isset($example->p2));//Because p2 is uninitialized,so result is bool(false)
echo $example->p2;//This time,__get is called by 1 time.
unset($example->p1);
var_dump(isset($example->p1));//Because of __unset,the result is bool(false)
echo $example->p1;//Amazing!I call the __get just once,but it seems that the __get method has bean called two times!
?>
————————
结果:
Call_get()!v1
bool(false) Call_get()!bool(false) Call_get()!Call_get()!
bool(false) Call_get()!bool(false) Call_get()!Call_get()! function __get($elmname){
echo "Call_get()!"; //In order to follow the method __get()
return $this->$elmname; //Call_get()! 由此处产生,因为依然找不到 p1
}echo $example->p1;//Amazing!I call ... Call_get()! 由此处产生,因为找不到 p1建议你使用 debug_backtrace() 或 debug_print_backtrace() 跟踪一下
而且如果是那样的话,每次进入到 __get()中的 return $this->$elmname; 时
都会再次调用__get(),那岂不是会无限的递归调用?为什么只调用两次呢?
不过,你这是在冲击php的__get方法的使用方式,echo还有print之类的输出 会调用这个对象的__toString方法吧?
/*
__X accessors explanation: if we have __get and property that is not part of the properties array is
requested, we call __get handler. If it fails, we return uninitialized. if we have __set and property that is not part of the properties array is
set, we call __set handler. If it fails, we do not change the array. for both handlers above, when we are inside __get/__set, no further calls for
__get/__set for this property of this object will be made, to prevent endless
recursion and enable accessors to change properties array. if we have __call and method which is not part of the class function table is
called, we cal __call handler.
*/而且在处理逻辑中专门引入了一个 guards 变量用于“protects from __get/__set ... recursion”,这样看来,只要 __get() 被嵌套调用,就是 BUG 了。我实测的结果(php-5.2.12),如果根本没有定义 $var,或者定义成 public $var(然后再 unset),都只会调用一次 __get(),只有定义为 private $var(然后再 unset),才会调用两次。怎么看都像是个 BUG。不过话说回来,这个东西对一般的程序逻辑似乎也没啥不良影响,hehe
————————————————————————————————
基于CSDN论坛提供的插件扩展功能,自己做了个签名档工具,分享给大家,欢迎技术交流 :)
他的blog值得看看 http://www.laruence.com/
BTW,网页右边的tag cloud很酷
感谢helloyou0,我多了一条学习php的途径,通过这个途径我已经咨询过php开发组人员这个问题,具体的调用机制还要参看zend_object_handlers.c中的源码。感谢版主xuzuning给了我理解他们深奥解释的启发!
在此我初步解释一下:(可能还有些偏差)
p1:
当调用p1的时候,zend vm 将调用read_propery读属性方法,
在该方法内将检查属性信息property_info和属性作用域property_socpe,
此时p1在example->ce之外被调用,即在他的socpe之外访问了私有变量。
所以get_property_info方法会被拒绝,并返回 空,
但是zend vm会认为我们将试图访问一个public的"p1",
所以zend vm将为这个"p1"设置一个getter_guard然后调用__get去找"p1",第1次输出"call_get()!"
在本例的__get()方法中,最后尝试取了$this->p1,
此时,我们是在example类的内部访问p1,所以get_property_info方法成功,
并返回一个名为"\0Example\0p1"的属性信息,
但是因为__unset会使p1从$example->properties中移除,所以zend vm就会访问"\0Example\0p1",
并为"\0Example\0p1"设置一个getter_guard然后再次调用__get,所以第2次输出"call_get()!"
第二次调用__get时,通过发现gettter_guard仍然是"\0Example\0p1",
知道了是在__get中调用了__get所以不会无限循环递归,而是放弃,返回。
p2:
当调用p2的时候,zend vm 将调用read_propery读属性方法,
此时p2在他的socpe之外被访问了。
所以get_property_info方法会被拒绝,并返回 空,
但是zend vm会认为我们将试图访问一个public的"p2",
所以zend vm将为这个"p2"设置一个getter_guard然后调用__get去找"p2",输出"call_get()!"
在__get()方法中,尝试取$this->p2,
此时,我们是在example类的内部访问p2,所以get_property_info方法成功,
而且p2在$example->properties中,所以获取成功,返回p2的值,就就不必访问"\0Example\0p1"了!
大家明白了吗?
再此推荐一个blog给大家,就是这位牛人帮我耐心解释了这个问题的!
http://www.laruence.com
www.trip58.com/love/index.php
我怎么和一个很不相关的人测的结果也很好?
这个laruence确实挺不错!