Using print_r and var_dump with circular reference
Asked Answered
W

9

50

I'm using the MVC framework Symfony, and it seems a lot of the built-in objects I want to debug have circular references. This makes it impossible to print the variables with print_r() or var_dump() (since they follow circular references ad infinitum or until the process runs out of memory, whichever comes first).

Instead of writing my own print_r clone with some intelligence, are there better alternatives out there? I only want to be able to print a variable (object, array or scalar), either to a log file, http header or the web page itself.

Edit: to clarify what the problem is, try this code:

<?php

class A
{
    public $b;
    public $c;

    public function __construct()
    {
        $this->b = new B();
        $this->c = new C();
    }
}

class B
{
    public $a;

    public function __construct()
    {
        $this->a = new A();
    }
}

class C
{
}

ini_set('memory_limit', '128M');
set_time_limit(5);

print_r(new A());
#var_dump(new A());
#var_export(new A());

It doesn't work with print_r(), var_dump() or var_export(). The error message is:

PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 523800 bytes) in print_r_test.php on line 10

Withdrew answered 9/6, 2011 at 11:36 Comment(3)
Not getting it... PHP 5.3.5 throws "Fatal error: Maximum function nesting level of '100' reached, aborting!" in constructor of B, where new A is created, which in turn creates new B, which again creates new A etc.Imhoff
Try creating new A without using print_r() on the result, i.e., use $obj = new A() instead of print_r(new A()). Do you still get the same error?Imhoff
print_r seems to now show circular references with RECURSION. Would this allowed memory size exhausted issue still occur with print_r?Edveh
S
12

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.

Sidon answered 9/6, 2011 at 13:1 Comment(1)
Works perfectly also with Drupal!Masha
A
15

Doctrine have the same service class.

Example of usage:

<?php echo "<pre>"; \Doctrine\Common\Util\Debug::dump($result, 4); echo "</pre>";?>
Akeyla answered 4/6, 2013 at 10:41 Comment(1)
This package is deprecated - and it refers to sympony/var-dumper instead.Rabbinate
S
12

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.

Sidon answered 9/6, 2011 at 13:1 Comment(1)
Works perfectly also with Drupal!Masha
L
7

TVarDumper

TVarDumper is intended to replace the buggy PHP function var_dump and print_r, since it can correctly identify the recursively referenced objects in a complex object structure. It also has a recursive depth control to avoid indefinite recursive display of some peculiar variables.

Check TVarDumper.php:

<?php
/**
 * TVarDumper class file
 *
 * @author Qiang Xue <[email protected]>
 * @link http://www.pradosoft.com/
 * @copyright Copyright &copy; 2005-2013 PradoSoft
 * @license http://www.pradosoft.com/license/
 * @version $Id$
 * @package System.Util
 */

/**
 * TVarDumper class.
 *
 * TVarDumper is intended to replace the buggy PHP function var_dump and print_r.
 * It can correctly identify the recursively referenced objects in a complex
 * object structure. It also has a recursive depth control to avoid indefinite
 * recursive display of some peculiar variables.
 *
 * TVarDumper can be used as follows,
 * <code>
 *   echo TVarDumper::dump($var);
 * </code>
 *
 * @author Qiang Xue <[email protected]>
 * @version $Id$
 * @package System.Util
 * @since 3.0
 */
class TVarDumper
{
    private static $_objects;
    private static $_output;
    private static $_depth;

    /**
     * Converts a variable into a string representation.
     * This method achieves the similar functionality as var_dump and print_r
     * but is more robust when handling complex objects such as PRADO controls.
     * @param mixed variable to be dumped
     * @param integer maximum depth that the dumper should go into the variable. Defaults to 10.
     * @return string the string representation of the variable
     */
    public static function dump($var,$depth=10,$highlight=false)
    {
        self::$_output='';
        self::$_objects=array();
        self::$_depth=$depth;
        self::dumpInternal($var,0);
        if($highlight)
        {
            $result=highlight_string("<?php\n".self::$_output,true);
            return preg_replace('/&lt;\\?php<br \\/>/','',$result,1);
        }
        else
            return self::$_output;
    }

    private static function dumpInternal($var,$level)
    {
        switch(gettype($var))
        {
            case 'boolean':
                self::$_output.=$var?'true':'false';
                break;
            case 'integer':
                self::$_output.="$var";
                break;
            case 'double':
                self::$_output.="$var";
                break;
            case 'string':
                self::$_output.="'$var'";
                break;
            case 'resource':
                self::$_output.='{resource}';
                break;
            case 'NULL':
                self::$_output.="null";
                break;
            case 'unknown type':
                self::$_output.='{unknown}';
                break;
            case 'array':
                if(self::$_depth<=$level)
                    self::$_output.='array(...)';
                else if(empty($var))
                    self::$_output.='array()';
                else
                {
                    $keys=array_keys($var);
                    $spaces=str_repeat(' ',$level*4);
                    self::$_output.="array\n".$spaces.'(';
                    foreach($keys as $key)
                    {
                        self::$_output.="\n".$spaces."    [$key] => ";
                        self::$_output.=self::dumpInternal($var[$key],$level+1);
                    }
                    self::$_output.="\n".$spaces.')';
                }
                break;
            case 'object':
                if(($id=array_search($var,self::$_objects,true))!==false)
                    self::$_output.=get_class($var).'#'.($id+1).'(...)';
                else if(self::$_depth<=$level)
                    self::$_output.=get_class($var).'(...)';
                else
                {
                    $id=array_push(self::$_objects,$var);
                    $className=get_class($var);
                    $members=(array)$var;
                    $keys=array_keys($members);
                    $spaces=str_repeat(' ',$level*4);
                    self::$_output.="$className#$id\n".$spaces.'(';
                    foreach($keys as $key)
                    {
                        $keyDisplay=strtr(trim($key),array("\0"=>':'));
                        self::$_output.="\n".$spaces."    [$keyDisplay] => ";
                        self::$_output.=self::dumpInternal($members[$key],$level+1);
                    }
                    self::$_output.="\n".$spaces.')';
                }
                break;
        }
    }
}

XDebug var_dump

Use XDebug PHP extension, and it'll detect and ignore the circular references, e.g.:

echo xdebug_var_dump($object);

print_r + array_slice

As per this post, you may try:

print_r(array_slice($desiredArray, 0, 4));

features_var_export

Use the following function which is part of Features module for Drupal (features.export.inc):

/**
 * Export var function
 */
function features_var_export($var, $prefix = '', $init = TRUE, $count = 0) {
  if ($count > 50) {
    // Recursion depth reached.
    return '...';
  }

  if (is_object($var)) {
    $output = method_exists($var, 'export') ? $var->export() : features_var_export((array) $var, '', FALSE, $count+1);
  }
  else if (is_array($var)) {
    if (empty($var)) {
      $output = 'array()';
    }
    else {
      $output = "array(\n";
      foreach ($var as $key => $value) {
        // Using normal var_export on the key to ensure correct quoting.
        $output .= "  " . var_export($key, TRUE) . " => " . features_var_export($value, '  ', FALSE, $count+1) . ",\n";
      }
      $output .= ')';
    }
  }
  else if (is_bool($var)) {
    $output = $var ? 'TRUE' : 'FALSE';
  }
  else if (is_int($var)) {
    $output = intval($var);
  }
  else if (is_numeric($var)) {
    $floatval = floatval($var);
    if (is_string($var) && ((string) $floatval !== $var)) {
      // Do not convert a string to a number if the string
      // representation of that number is not identical to the
      // original value.
      $output = var_export($var, TRUE);
    }
    else {
      $output = $floatval;
    }
  }
  else if (is_string($var) && strpos($var, "\n") !== FALSE) {
    // Replace line breaks in strings with a token for replacement
    // at the very end. This protects whitespace in strings from
    // unintentional indentation.
    $var = str_replace("\n", "***BREAK***", $var);
    $output = var_export($var, TRUE);
  }
  else {
    $output = var_export($var, TRUE);
  }

  if ($prefix) {
    $output = str_replace("\n", "\n$prefix", $output);
  }

  if ($init) {
    $output = str_replace("***BREAK***", "\n", $output);
  }

  return $output;
}

Usage:

echo features_var_export($object);

Serialize

Use serialize to dump the object in serialized representation, e.g.:

echo serialize($object);

JSON Encode

Use json_encode to convert it into JSON format, e.g.:

echo json_encode($object);

See also: Test if variable contains circular references

Lanate answered 7/4, 2016 at 15:23 Comment(0)
I
5

This seemed to get the job done for me:

print_r(json_decode(json_encode($value)));
Incivility answered 1/3, 2017 at 21:16 Comment(1)
Works for me, not very neat, but does what I need. Thanks!Station
B
3

You could use var_export().

var_export() does not handle circular references as it would be close to impossible to generate parsable PHP code for that. If you want to do something with the full representation of an array or object, use serialize().

UPDATE: Seems like I was wrong. I thought I used this function a while ago for this purpose, but it must have been some drunken imagination.

This way, the only advice I can give is installing Xdebug.

Breda answered 9/6, 2011 at 11:43 Comment(4)
I would not use var_export(). I believe "does not handle" means "cannot detect" or something like that. On PHP 5.3.5, var_export() results in Fatal error: Nesting level too deep - recursive dependency?.Imhoff
It doesn't work: "Fatal error: Nesting level too deep - recursive dependency?"Freitas
This post offers some insight, it has to do with the way non-strict comparison of objects is performed in different versions of php richardlord.net/blog/…Spellbinder
"it must have been some drunken imagination" - I've had a bad case of Ballmer's peak lately too brother.Roentgenology
Q
2

I had this problem too and i solved it by implementing the __get() Method to break the reference circle. The __get() Method is called AFTER an attribute isnt found in the class declaration. The __get() Method also gets the name of the missing attribute. Using this you can define "virtual attributes" that work kind of the same way as usual ones but arent mentioned by the print_r function. Here an example:

public function __get($name)
{
    if ($name=="echo") {
        return Zend_Registry::get('textConfig');
    }

}

Quenelle answered 21/9, 2012 at 11:1 Comment(0)
I
1
class Test {
    public $obj;
}
$obj = new Test();
$obj->obj = $obj;
print_r($obj);
var_dump($obj);

Output:

Test Object
(
    [obj] => Test Object
 *RECURSION*
)

object(Test)[1]
  public 'obj' => 
    &object(Test)[1]

It seems to me that both print_r() and var_dump() can handle recursion with no problems. Using PHP 5.3.5 on Windows.


var_export() does not detect recursion, which results in instant fatal error:

Fatal error:  Nesting level too deep - recursive dependency? in \sandbox\index.php on line 28
Imhoff answered 9/6, 2011 at 11:47 Comment(3)
Doesn't always work. I'm facing the same issue. debug_backtrace() object can't be printed due to circular referenceMacaulay
@kervin, which version of PHP do you use?Imhoff
It would be nice to know when "not always" is. For me print_r also worked fine until recently. It just generated RECURSION for recursion. But since some time (PHP version?) this doesn't seem to work any more.Lippe
F
1

Symfony nowadays also has VarDumer component: https://symfony.com/doc/current/components/var_dumper.html

It handles circular references and supports remote dump server.

Installation is pretty easy:

composer require symfony/var-dumper --dev

Then you can use global function dump (I suppose composer's autoload.php is already included):

<?php
/* ... */
dump($someVar);
Felicitous answered 19/9, 2018 at 7:56 Comment(0)
K
1

Came across this in 2022, hope this helps someone;

PHP has a magic method you could implement into your class to clean up recursive objects:

__debugInfo()

Here's a good starting point for your function:

public function __debugInfo(){
    // clone the object so you can modify as needed
    $object = clone($this);
    // overwrite properties with meaningful info
    $object->recursive_object_prop = 'Removed for debugging...';
    // return a typecasted array, __debugInfo() requires it.
    return (Array) $object; 
}

You could also remove properties entirely after typecasting $object using unset() on the resulting array.

Kristankriste answered 1/8, 2022 at 16:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.