Using print_r and var_dump with circular reference

2019-01-14 00:42发布

问题:

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

回答1:

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.



回答2:

Doctrine have the same service class.

Example of usage:

<?php echo "<pre>"; \Doctrine\Common\Util\Debug::dump($result, 4); echo "</pre>";?>


回答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.



回答4:

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 <qiang.xue@gmail.com>
 * @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 <qiang.xue@gmail.com>
 * @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



回答5:

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


回答6:

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

}



回答7:

This seemed to get the job done for me:

print_r(json_decode(json_encode($value)));


回答8:

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