Debugging Variables
Sometimes you want to inspect the contents of variables more closely, by looking at its internal properties. For this, PHP has the debug_zval_dump function.
The internal representation of a PHP variable container (called zval), contains the type and value of a variable, but also whether it is a reference and what its refcount is. Due to PHP's copy-on-write policy, one specific zval container can be used by multiple variables at the same time as we will see in a bit.
The refcount field contains how many symbols (variable names) point to a zval, regardless of whether a reference set is used or not. For example, both the following examples will have as end-result: two symbols and one zval:
<?php $a = 42; // one symbol: 'a'; one zval: int(42) $b = $a; // two symbols: 'a', 'b'; one zval: int(42) ?> <?php $a = 42; // one symbol: 'a'; one zval: int(42) $b =& $a; // two symbols: 'a', 'b'; one zval: int(42) ?>
In order for PHP to know the difference, there is an extra flag on the zval: is_ref, which is set if you assign a variable by reference with =&. It is important to know which of the situations actually occurred because PHP needs to be able to do the correct thing when a third symbol comes into play. In the first situation, a third symbol can be associated with the same zval only if it is assigned by assignment. If the third symbol is assigned by reference with something like $c =& $b; then PHP needs to duplicate (split) the zval into two zvals because only symbol b and c are linked, whereas a is distinct. Without the separation PHP will not be able to know which of the two symbols are referenced (b and c), and which one is not part of the reference set (a).
The opposite happens in the second situation. If a third symbol is assigned by reference with something like $c =& $b; then PHP can just update the refcount on the zval. If the third symbol is assigned by value with $c =
$b then PHP needs to split the zval so that a and b still point to the original zval, and c to the newly created one without having links to either a or b.
This brings us to the very simple rule for when PHP needs to split upon assignment:
-
If the type of assigning (by-value with
=or by-ref with=&) matches the value ofis_ref: just updaterefcountby adding 1. -
If the type of assigning does not match the value of
is_ref: split the zval.
Now let us get back to the original issue: the inspection of the internal structure of variable contents with debug_zval_dump. When a variable is passed to a function as an argument, an assignment to the function's local scope is made; just like an assignment in a function. Which means that the same splitting rules apply. The following are (mostly) equivalent regarding on what happens to the is_ref and refcount values:
<?php $a = 42; $b = $a; // assignment in local scope ?>
and:
<?php
function foo($b)
{
}
$a = 42;
foo($a); // assignment to $b in the function's scope
?>
PHP's debug_zval_dump takes a variable as argument for analysis and is thus also bound to the same splitting rules as outlined earlier. This means that's not really well suited for dumping a zval's internal structure as it would always modify the zval. Besides adding 1 to the refcount field, it could also force a split resulting in unexpected output:
<?php $a = 42; debug_zval_dump($a); ?>
shows:
long(42) refcount(2)
Xdebug has a similar function to display a zval's internal data: xdebug_debug_zval. This function does requires not a variable to be passed as its argument, but instead requires a variable name to be passed in. With this, the manipulation of the zval is avoided and the proper values are shown:
<?php
$a = 42;
xdebug_debug_zval('a');
?>
which shows:
a: (refcount=1, is_ref=0)=42
This is what is expected, and as bonus, it also shows the is_ref flag. PHP's bug #53895 mentions a few issues with the debug_zval_dump function. Gustavo is quite right to say that passing the variable name by-ref is not going to work, and any form of this function where the variable is passed in is not going to work. Johannes however is also right that passing just the variable name as a string might not be good enough; as you can't directly dump the zval information for for example array keys. There is an Xdebug feature request for this functionality to be implemented however. If implemented, you should be able to debug the foo array key with something like:
<?php
$a = array('foo' => 3.1415);
xdebug_debug_zval('a["foo"]');
?>
Now if I only had some spare time...
Comments
Great Post, something I may want to admit is have not used this function nor heard :-) . So Thanks for the introduction too. I saw a book about PHP written by you, Andi and some one else . Do you have any plans of rewriting it for 5.3 ? :-)
@Hari: I can't speak for Andi and Stig, but I don't think I'll be updating PHP 5 Power Programming for PHP 5.3.
Wow Thanks for your quick reply. I didn't have noticed the book until I saw one in my friends Hand . I want to admit that it was one of the wonderful books I have ever read in PHP. Something with good use cases , where, when and how to apply. I never have also noticed someone promoting the book too. Anyway it was really a great work done on it.
Life Line
I walked 3.5km in 35m31s
Created a main entrance and a home entrance
Created an entrance
Updated a house building
Created an entrance
I walked 5.8km in 1h15m06s
I've just finished reading "A Cheese-Monger's Tour de France", by Ned Palmer.
Now I want to try many of those! 🧀
I'm thrilled to announce that I'll be speaking at the 23rd edition of #phpday, the international PHP conference in Italy, organised by @grusp.
I’ll be presenting a talk titled: "Better Debugging With Xdebug".
It's in Verona, Italy, on May 14-15th 2026.
You can use my speaker’s discount code "speaker_10OFF" for 10% off at https://www.phpday.it/tickets/?utm_medium=organic&utm_source=linkedin&utm_campaign=post-speaker
I walked 5.5km in 1h11m00s
I walked 1.1km in 9m37s
Merged pull request #1066
PHP 8.6: Changes to opcache optimisations wrt function arguments
I walked 10.5km in 1h49m54s
Fixed building type
Fixed addresses and building type
Updated a bus_stop, a waste_basket, and a bench
Created 2 waste_baskets; Updated 2 bus_stops and a crossing; Confirmed a dry_cleaning shop
I walked 8.5km in 1h29m59s
Merge branch 'v2022'
Update data to 2026a
I walked 5.6km in 1h8m16s
I walked 9.5km in 2h15m36s
Sigh, the AI Slop has now come for the PHP project.
I had this little Black-capped Chickadee eating out of my hand earlier on a lovely 8k walk with @dseguy and @DaveLiddament in the snow at the back end of @ConFooCa .
Thanks Canada!
Updated a restaurant



Shortlink
This article has a short URL available: https://drck.me/debug-vars-8jc