代码:
<?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()函数就会不正常的执行啊?能告诉我这里面具体的调用机制么?

解决方案 »

  1.   

    当读取类的不存在的属性时,__get 方法就会被调用
    你遇到的正是这个,没有什么不可理解的
      

  2.   

    可是为什么最后一次读取$example->p1会调用两次__get方法呢?
    而之前也是读取unset过的$example->p2属性,却只调用过一次!
      

  3.   

    我测试你的代码,并没有发现你说的现象
    估计是你自己没有弄清楚的原因,建议在你调试输出时加上行号 __LINE__
      

  4.   

    路过 的!不过 想告诉楼主 bug不是随便说的 先自己搞清楚 看看是不是自己的逻辑上有哪些问题
      

  5.   

    您测试的结果不是这样么?
    ——————————————————————
    vs
    bool(true) bool(false) 调用get啦!保密
    调用get啦!保密bool(false) 调用get啦!调用get啦!
    已经被摧毁!
    ——————————————————————
    他最后在bool(false)后输出了两遍 调用get啦!调用get啦!
      

  6.   

    多谢您的指导,我echo输出时添加了__LINE__结果是
    36vs
    bool(true) bool(false) 调用get啦!40保密
    调用get啦!41保密bool(false) 调用get啦!调用get啦!44
    已经被摧毁!
    最后一个44行虽然输出了两次 调用get啦!调用get啦! 但是却只echo了一次!
    这是问什么?
      

  7.   

    p2的时候没有什么特别,
    因为这句:
    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,也会有人解释给你的谢谢,如果你比较忙不打算发过去,麻烦这里说一声,我会发上去问问。 
      

  8.   

    多谢啦!请帮我问一下吧!
    我不知道应该选那个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()!
      

  9.   

    Call_get()!v1
    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() 跟踪一下
      

  10.   

    那为什么echo $example->p2的时候只产生了一次Call_get()!呢?
    而且如果是那样的话,每次进入到 __get()中的 return $this->$elmname; 时
    都会再次调用__get(),那岂不是会无限的递归调用?为什么只调用两次呢?
      

  11.   

    看来你想法不错啊!
    不过,你这是在冲击php的__get方法的使用方式,echo还有print之类的输出   会调用这个对象的__toString方法吧?
      

  12.   

    额……我是php初学者,调不调用__toString我不知道,但是即使我不echo直接$example->p1;也会输出Call_get()!
      

  13.   

    貌似还真是一个 BUG。我在 PHP 的源代码里翻了一下(php-5.2.12-src),在 Zend/zend_object_handlers.c 的开头部分看到了如下注释:
    /*
      __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论坛提供的插件扩展功能,自己做了个签名档工具,分享给大家,欢迎技术交流 :)
      

  14.   

    那$example->p2为什么没有再次调用__get()呢?
      

  15.   

    这个是楼主发的吧?https://bugs.php.net/bug.php?id=55731回答疑问的也是中国人:) 
    他的blog值得看看 http://www.laruence.com/
    BTW,网页右边的tag cloud很酷
      

  16.   

    感谢这些天大家对我的帮助!虽然没有人给出最终的答案,但你们给我的一些思考问题的方法对我很有启发!
    感谢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
      

  17.   

    问一下大家,请问你们相信下面这样的缘分测试吗?
    www.trip58.com/love/index.php
    我怎么和一个很不相关的人测的结果也很好?
      

  18.   

    是的!呵呵!我刚结贴才发现你的回复!
    这个laruence确实挺不错!