Detecting whether a PHP variable is a reference / referenced

后端 未结 5 920
情书的邮戳
情书的邮戳 2020-11-27 05:33

Is there a way in PHP to determine whether a given variable is a reference to another variable and / or referenced by another variable? I appreciate that it might not be po

相关标签:
5条回答
  • 2020-11-27 05:46

    Take a peak at xdebug_debug_zval(). Right now, that's the only way to really know if you can determine everything about the variable's zval.

    So here are a couple of helper functions to determine some helpful information:

    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]);
    }
    

    So with some sample variables:

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

    We can test if a variable is a reference:

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

    We can get the number of variables linked to the zval (not necessarily a reference, can be for copy-on-write):

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

    We can test if we can copy-on-write (copy without performing a memory copy):

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

    And we can test if we can make a reference without copying the zval:

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

    And now, we can check if a variable references itself through some black magic:

    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;
    }
    

    It's a bit hacky since we can't determine what other symbols reference the same zval (only that other symbols reference). So this basically checks to see if $a is a reference, and if $a and $b both have the same refcount and reference flag set. Then, it changes one to check if the other changes (indicating they are the same reference).

    0 讨论(0)
  • 2020-11-27 05:48

    Full working example:

    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
    
    0 讨论(0)
  • 2020-11-27 05:53

    Maybe xdebug_debug_zval() helps you. http://www.xdebug.org/docs/all_functions

    0 讨论(0)
  • 2020-11-27 06:01

    You can use 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
    

    This though will not work anymore as of PHP 5.4 as they removed call time pass by reference support and may throw an E_STRICT level error on lower versions.

    If you wonder, where the -4 in the above function come from: You tell me... I got it by trying. In my eyes it should be only 3 (the variable, the variable in my function, the variable passed to zend_debug_zval), but I'm not too good at PHP internals and it seems that it creates yet another reference somewhere on the way ;)

    0 讨论(0)
  • 2020-11-27 06:08

    Edit: It seems I've answered the question 'is it possible to check if two variables are referencing same value in memory' not the actual question asked. :P


    As far as 'plain' variables go the answer is 'no'.

    As far as objects go - maybe.

    All objects are by default handled by references. Also each object has it's serial number which you can see when you var_dump() it.

    >> class a {};
    >> $a = new a();
    >> var_dump($a);
    
    object(a)#12 (0) {
    }
    

    If you could get somehow to this #, you could effectively compare it for two variables, and see if they point to the same object. The question is how to get this number. var_export() does not return it. I don't see snything in Reflection classes that would get it either.

    One thing that comes to my mind is using output buffering + regex

    0 讨论(0)
提交回复
热议问题