【问题标题】:Detecting whether a PHP variable is a reference / referenced检测 PHP 变量是否为引用/被引用
【发布时间】:2011-06-16 14:19:34
【问题描述】:

PHP 中有没有办法确定给定变量是否是对另一个变量的引用和/或是否被另一个变量引用?我明白,鉴于 php.net 上的 comment 设置 $a=& $b 意味着“$a 和 $b 在这里完全相等,因此可能无法将检测“引用”和“引用自”分开。$ a 没有指向 $b,反之亦然。$a 和 $b 指向同一个地方。"

如果无法确定给定变量是否为引用/被引用,是否有确定两个变量是否相互引用的通用方法?同样,php.net 上的comment 提供了一个用于进行此类比较的函数——尽管它涉及编辑其中一个变量并查看另一个变量是否受到类似影响。如果可能的话,我宁愿避免这样做,因为我正在考虑的一些变量会大量使用魔法 getter/setter。

本例中请求的背景是编写一个调试函数来帮助详细查看结构。

【问题讨论】:

标签: php php-internals


【解决方案1】:

编辑: 似乎我已经回答了“是否可以检查两个变量是否在内存中引用相同的值”这个问题,而不是实际提出的问题。 :P


就“普通”变量而言,答案是否定的。

就物体而言——也许吧。

默认情况下,所有对象都由引用处理。每个对象都有它的序列号,当你var_dump()它时你可以看到它。

>> class a {};
>> $a = new a();
>> var_dump($a);

object(a)#12 (0) {
}

如果你能以某种方式得到这个#,你就可以有效地比较两个变量,看看它们是否指向同一个对象。问题是如何获得这个数字。 var_export() 不返回它。我在Reflection 类中也看不到任何可以得到它的东西。

我想到的一件事是使用输出缓冲 + 正则表达式

【讨论】:

  • 捕获来自var_export($variable,TRUE)的输出,然后一个小的正则表达式应该捕获数字
  • @KristofferSall-Storgaard:仅适用于print_r()
【解决方案2】:

也许 xdebug_debug_zval() 可以帮助你。 http://www.xdebug.org/docs/all_functions

【讨论】:

    【解决方案3】:

    你可以使用debug_zval_dump:

    function countRefs(&$var) {
        ob_start();
        debug_zval_dump(&$var);
        preg_match('~refcount\((\d+)\)~', ob_get_clean(), $matches);
        return $matches[1] - 4;
    }
    
    $var = 'A';
    echo countRefs($var); // 0
    
    $ref =& $var;
    echo countRefs($var); // 1
    

    这从 PHP 5.4 起不再起作用,因为它们删除了通过引用传递的调用时间支持,并且可能会在较低版本上引发 E_STRICT 级别错误。

    如果你想知道,上述函数中的-4 来自哪里:你告诉我...我通过尝试得到它。在我看来它应该只有 3(变量,我的函数中的变量,传递给 zend_debug_zval 的变量),但我不太擅长 PHP 内部,似乎它在途中的某个地方创建了另一个引用;)

    【讨论】:

    • $var = 'A' 是 1 $ref = &$var; 是 2,那么 countRefs($var) 是 3,debug_zval_dump(&$var); 是 4
    • @Mchl:带有$reg = &$var 的代码将给出5,而不是44 是没有引用的代码。这就是为什么我没有得到号码。
    【解决方案4】:

    xdebug_debug_zval() 上达到高潮。现在,这是真正知道您是否可以确定有关变量 zval 的所有内容的唯一方法。

    所以这里有几个帮助函数来确定一些有用的信息:

    function isRef($var) {
        $info = getZvalRefCountInfo($var);
        return (boolean) $info['is_ref'];
    }
    function getRefCount($var) {
        $info = getZvalRefCountInfo($var);
        return $info['refcount'];
    }
    function canCopyOnWrite($var) {
        $info = getZvalRefCountInfo($var);
        return $info['is_ref'] == 0;
    }
    function canReferenceWithoutCopy($var) {
        $info = getZvalRefCountInfo($var);
        return $info['is_ref'] == 1 || $info['refcount'] == 1;
    }
    
    function getZvalRefCountInfo($var) {
        ob_start();
        xdebug_debug_zval($var);
        $info = ob_get_clean();
        preg_match('(: \(refcount=(\d+), is_ref=(\d+)\))', $info, $match);
        return array('refcount' => $match[1], 'is_ref' => $match[2]);
    }
    

    所以有一些示例变量:

    $a = 'test';
    $b = $a;
    $c = $b;
    $d =& $c;
    $e = 'foo';
    

    我们可以测试一个变量是否是一个引用:

    isRef('a'); // false
    isRef('c'); // true
    isRef('e'); // false
    

    我们可以得到链接到zval的变量个数(不一定是引用,可以用于copy-on-write):

    getRefCount('a'); // 2
    getRefCount('c'); // 2
    getRefCount('e'); // 1
    

    我们可以测试我们是否可以写时复制(复制而不执行内存复制):

    canCopyOnWrite('a'); // true
    canCopyOnWrite('c'); // false
    canCopyOnWrite('e'); // true
    

    我们可以测试是否可以在不复制 zval 的情况下进行引用:

    canReferenceWithoutCopy('a'); // false
    canReferenceWithoutCopy('c'); // true
    canReferenceWithoutCopy('e'); // true
    

    现在,我们可以通过一些黑魔法检查变量是否引用了自身:

    function isReferenceOf(&$a, &$b) {
        if (!isRef('a') || getZvalRefCountInfo('a') != getZvalRefCountInfo('b')) {
            return false;
        }
        $tmp = $a;
        if (is_object($a) || is_array($a)) {
            $a = 'test';
            $ret = $b === 'test';
            $a = $tmp;
        } else {
            $a = array();
            $ret = $b === array();
            $a = $tmp;
        }
        return $tmp;
    }
    

    这有点棘手,因为我们无法确定哪些其他符号引用了相同的 zval(只有其他符号引用)。所以这基本上检查$a 是否是一个引用,以及$a$b 是否都具有相同的引用计数和引用标志集。然后,它会更改一个以检查其他是否更改(表明它们是相同的引用)。

    【讨论】:

      【解决方案5】:

      完整的工作示例:

      function EqualReferences(&$first, &$second){
          if($first !== $second){
              return false;
          } 
          $value_of_first = $first;
          $first = ($first === true) ? false : true; // modify $first
          $is_ref = ($first === $second); // after modifying $first, $second will not be equal to $first, unless $second and $first points to the same variable.
          $first = $value_of_first; // unmodify $first
          return $is_ref;
      }
      
      $a = array('foo');
      $b = array('foo');
      $c = &$a;
      $d = $a;
      
      var_dump(EqualReferences($a, $b)); // false
      var_dump(EqualReferences($b, $c)); // false
      var_dump(EqualReferences($a, $c)); // true
      var_dump(EqualReferences($a, $d)); // false
      var_dump($a); // unmodified
      var_dump($b); // unmodified
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-12-11
        • 1970-01-01
        • 1970-01-01
        • 2010-12-05
        • 1970-01-01
        • 2011-05-22
        • 2021-09-23
        相关资源
        最近更新 更多