Using Print_R and Var_Dump with Circular Reference

Using print_r and var_dump with circular reference

We are using the PRADO Framework and it has a built in class called "TVarDumper" which can handle such complex objects pretty well - it even can format it in nice HTML incl. Syntax Highlighting. You can get that class from HERE.

What would cause a print_r and/or a var_dump to fail debugging a variable?

First, PHP never "just white pages". When you get a blank screen, that means PHP's execution has halted fro some reason. However, unless your server has been configured to not log errors, the PHP error log or the Magento exception log should have an error for you.

As far as your specific problem goes, many of Magento's objects contain reference to a large amount of information — and sometimes the references are circular. PHP's var_dump and print_r functions will blindly follow these circular references and attempt to print everything out. This eventually leads to PHP using more memory than is allowed by the memory_limit ini setting, and execution halts.

Most PHP professionals use the xDebug extension to work around this. The xDebug extension has a modified var_dump that will limit the amount of information dumped, which prevents the above memory limit problems. The xdebug.var_display_max_children, xdebug.var_display_max_data, and xdebug.var_display_max_depth ini settings are the ones you'll want to tweak if xDebug's still not helping with the memory limit problem. (some PHP distributions have these set too high initially)

If that's not a possibility, a little caution with your var_dump's can still help.

Use this to figure out the variable type

var_dump(get_class($thing));

If it's a Magento object, use this to see its data keys

var_dump(array_keys($thing->getData()));

And then output individual data members with

var_dump($thing->getData('key_name'));
var_dump($thing->getKeyName()));

How to get proper debug context from production PHP code? print_r vs var_export vs var_dump corner cases

The comment from @BlackXero is correct and works for me.

I did not find a build-in printing function which does not cause errors / warnings when containing a mysqli object with a closed connection (which I would actually classify as bug / unwanted behavior).

We ended up adding the Symfony Vardumper via

composer require symfony/var-dumper

and writing a little helper function for displaying proper and nice output both from cli scripts or the browser:

use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;

class Debug {
/**
* Method provides a function which can handle all our corner-cases for producing
* debug output.
*
* The corner cases are:
* - objects with recursion
* - mysqli references (also to closed connections in error handling)
*
* The returned result will be:
* - formatted for CLI if the script is run from cli
* - HTML formatted otherwise
* - The HTML formatted output is collapsed by default. Use CTRL-left click to
* expand/collapse all children
* - You can force html|cli formatting using the optional third parameter
*
* Uses the Symfony VarDumper composer module.
*
* @see https://github.com/symfony/var-dumper
* @see https://stackoverflow.com/questions/57520457/how-to-get-proper-debug-context-from-production-php-code-print-r-vs-var-export
* @param mixed $val - variable to be dumped
* @param bool $return - if true, will return the result as string
* @param string|null $format null|cli|html for forcing output format
* @return bool|string
*/
public static function varDump($val, $return = false, $format = null) {
if (is_null($format)) {
$format = php_sapi_name() == 'cli' ? 'cli' : 'html';
}
$cloner = new VarCloner();
if ($format === 'cli') {
$dumper = new CliDumper();
} else {
$dumper = new HtmlDumper();
}

$output = fopen('php://memory', 'r+b');
$dumper->dump($cloner->cloneVar($val), $output);
$res = stream_get_contents($output, -1, 0);

if ($return) {
return $res;
} else {
echo $res;
return true;
}
}
}

That method

  • can handle all input I pass to it without errors or warnings
  • format nicely for both CLI and HTML
  • return the result as a string for forwarding it to external error tracking systems like sentry

So it ticks all boxes I asked for in the initial question.

Thanks @BlackXero for understanding the question correctly and pointing me in the right direction.

Test if variable contains circular references

Would this do it?

function isRecursive($array) {
foreach($array as $v) {
if($v === $array) {
return true;
}
}
return false;
}

PHP's var_dump / print_r output is garbled - encoding issue?

For what it's worth, I finally got to the bottom of this problem (I think!)

The problem seems to be that the API's output was being run through json_decode whether it was JSON or not. MySQL errors were causing an error page, not a JSON response, which when run through json_decode (by the API-handling code that received it) before var_dump produced garbled character salad, as above.

How can I capture the result of var_dump to a string?

Use output buffering:

<?php
ob_start();
var_dump($someVar);
$result = ob_get_clean();
?>

How to check for circular references in PHP when recursively parsing an associative array?

An adapted version of your code, using the strict in_array check from the answer linked by Ryan Vincent, is shown below:

function HTMLStringify($arr, array $seen = array()) {
if (is_array($arr)) {
$seen[] = $arr;
$html = '<ul>';

foreach ($arr as $key => $value) {
$html .= '<li>' . $key;

if (is_array($value)) {

if (in_array($value, $seen, true)) {
// Deal with recursion in your own way here
$html .= ' [RECURSION]';
} else {
$html .= HTMLStringify($value, $seen);
}

} elseif (is_numeric($value) || is_string($value) || is_null($value)) {
$html .= ' = ' . $value;
} else {
$html .= ' [couldn\'t parse ' . gettype($value) . ']';
}

$html .= '</li>';
}

return $html . '</ul>';
} else {
return null;
}
}

$arr = array(1 => 'one', 2 => 'two');
$arr[3] = &$arr;
echo HTMLStringify($arr);

Comparing across a number of PHP versions, it looks like this will work for PHP 5.3.15+ and PHP 5.4.5+.

Is there a way to detect circular arrays in pure PHP?

The isRecursiveArray(array) method below detects circular/recursive arrays. It keeps track of which arrays have been visited by temporarily adding an element containing a known object reference to the end of the array.

If you want help writing the serialization method, please update your topic question and provide a sample serialization format in your question.

function removeLastElementIfSame(array & $array, $reference) {
if(end($array) === $reference) {
unset($array[key($array)]);
}
}

function isRecursiveArrayIteration(array & $array, $reference) {
$last_element = end($array);
if($reference === $last_element) {
return true;
}
$array[] = $reference;

foreach($array as &$element) {
if(is_array($element)) {
if(isRecursiveArrayIteration($element, $reference)) {
removeLastElementIfSame($array, $reference);
return true;
}
}
}

removeLastElementIfSame($array, $reference);

return false;
}

function isRecursiveArray(array $array) {
$some_reference = new stdclass();
return isRecursiveArrayIteration($array, $some_reference);
}

$array = array('a','b','c');
var_dump(isRecursiveArray($array));
print_r($array);

$array = array('a','b','c');
$array[] = $array;
var_dump(isRecursiveArray($array));
print_r($array);

$array = array('a','b','c');
$array[] = &$array;
var_dump(isRecursiveArray($array));
print_r($array);

$array = array('a','b','c');
$array[] = &$array;
$array = array($array);
var_dump(isRecursiveArray($array));
print_r($array);


Related Topics



Leave a reply



Submit