Detecting whether a PHP variable is a reference / referenced
Asked Answered
C

5

27

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 possible to separate detecting "reference to" and "reference from" given the comment on php.net that setting $a=& $b means "$a and $b are completely equal here. $a is not pointing to $b or vice versa. $a and $b are pointing to the same place."

If it's not possible to determine whether a given variable is a reference / referenced, is there a generalised way of determining if two variables are references of each other? Again, a comment on php.net supplies a function for doing such a comparison - although it is one that involves editing one of the variables and seeing if the other variable is similarly effected. I'd rather avoid doing this if possible since some of the variables I'm considering make heavy use of magic getters / setters.

The background to the request in this instance is to write a debugging function to help view structures in detail.

Countable answered 27/1, 2011 at 14:28 Comment(1)
You can check if two variables are references of each other: https://mcmap.net/q/226161/-detecting-whether-a-php-variable-is-a-reference-referencedBreban
P
8

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

Pinhead answered 27/1, 2011 at 15:0 Comment(2)
$var = 'A' is 1 $ref = &$var; is 2, then countRefs($var) is 3 and debug_zval_dump(&$var); is 4Scottyscotus
@Mchl: The code with $reg = &$var will give 5, not 4. The 4 is the code without the reference. That's why I don't get the number.Pinhead
B
8

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
Breban answered 7/8, 2013 at 17:54 Comment(0)
P
4

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

Paperweight answered 27/1, 2011 at 14:33 Comment(0)
G
2

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).

Gambado answered 20/10, 2011 at 15:15 Comment(0)
S
1

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

Scottyscotus answered 27/1, 2011 at 14:31 Comment(2)
capture the output from var_export($variable,TRUE) then a minor regex should capture the numberAllow
@KristofferSall-Storgaard: that only works for print_r()Offhand

© 2022 - 2024 — McMap. All rights reserved.