Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
112 views
in Technique[技术] by (71.8m points)

php - How can I use var_dump + output buffering without memory errors?

I'm using a debugging aid in an application that uses var_dump() with output buffering to capture variables and display them. However, I'm running into an issue with large objects that end up using up too much memory in the buffer.

function getFormattedOutput(mixed $var) {
  if (isTooLarge($var)) { 
    return 'Too large! Abort!'; // What a solution *might* look like
  }

  ob_start();
  var_dump($var); // Fatal error:  Allowed memory size of 536870912 bytes exhausted
  $data = ob_get_clean();

  // Return the nicely-formated data to use later
  return $data
}

Is there a way I can prevent this? Or a work-around to detect that it's about to output a gigantic amount of info for a particular variable? I don't really have control which variables get passed into this function. It could be any type.

question from:https://stackoverflow.com/questions/5446647/how-can-i-use-var-dump-output-buffering-without-memory-errors

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

As all the others are mentioning what you ask is impossible. The only thing you can do is try to handle it as good as possible.

What you can try is to split it up into smaller pieces and then combine it. I've created a little test to try and get the memory error. Obviously a real world example might behave differently, but this seems to do the trick.

<?php
define('mem_limit', return_bytes(ini_get('memory_limit'))); //allowed memory

/*
SIMPLE TEST CLASS
*/
class test { }
$loop = 260;
$t = new Test();
for ($x=0;$x<=$loop;$x++) {
  $v = 'test'.$x;
  $t->$v = new Test();
  for ($y=0;$y<=$loop;$y++) {
    $v2 = 'test'.$y;
    $t->$v->$v2 = str_repeat('something to test! ', 200);
  }
}
/* ---------------- */


echo saferVarDumpObject($t);

function varDumpToString($v) {
  ob_start();
  var_dump($v);
  $content = ob_get_contents();
  ob_end_clean();
  return $content;
}

function saferVarDumpObject($var) {
  if (!is_object($var) && !is_array($var))
    return varDumpToString($var);

  $content = '';
  foreach($var as $v) {
    $content .= saferVarDumpObject($v);
  }
  //adding these smaller pieces to a single var works fine.
  //returning the complete larger piece gives memory error

  $length = strlen($content);
  $left = mem_limit-memory_get_usage(true);

  if ($left>$length)
    return $content; //enough memory left

  echo "WARNING! NOT ENOUGH MEMORY<hr>";
  if ($left>100) {
    return substr($content, 0, $left-100); //100 is a margin I choose, return everything you have that fits in the memory
  } else {
    return ""; //return nothing.
  }  
}

function return_bytes($val) {
    $val = trim($val);
    $last = strtolower($val[strlen($val)-1]);
    switch($last) {
        // The 'G' modifier is available since PHP 5.1.0
        case 'g':
            $val *= 1024;
        case 'm':
            $val *= 1024;
        case 'k':
            $val *= 1024;
    }

    return $val;
}
?>

UPDATE The version above still has some error. I recreated it to use a class and some other functions

  • Check for recursion
  • Fix for single large attribute
  • Mimic var_dump output
  • trigger_error on warning to be able to catch/hide it

As shown in the comments, the resource identifier for a class is different from the output of var_dump. As far as I can tell the other things are equal.

<?php  
/*
RECURSION TEST
*/
class sibling {
  public $brother;
  public $sister;
}
$brother = new sibling();
$sister = new sibling();
$brother->sister = $sister;
$sister->sister = $brother;
Dump::Safer($brother);


//simple class
class test { }

/*
LARGE TEST CLASS - Many items
*/
$loop = 260;
$t = new Test();
for ($x=0;$x<=$loop;$x++) {
  $v = 'test'.$x;
  $t->$v = new Test();
  for ($y=0;$y<=$loop;$y++) {
    $v2 = 'test'.$y;
    $t->$v->$v2 = str_repeat('something to test! ', 200);
  }
}
//Dump::Safer($t);
/* ---------------- */


/*
LARGE TEST CLASS - Large attribute
*/
$a = new Test();
$a->t2 = new Test();
$a->t2->testlargeattribute = str_repeat('1', 268435456 - memory_get_usage(true) - 1000000);
$a->smallattr1 = 'test small1';
$a->smallattr2 = 'test small2';
//Dump::Safer($a);
/* ---------------- */

class Dump
{
  private static $recursionhash;
  private static $memorylimit;
  private static $spacing;
  private static $mimicoutput = true;


  final public static function MimicOutput($v) {
    //show results similar to var_dump or without array/object information
    //defaults to similar as var_dump and cancels this on out of memory warning
    self::$mimicoutput = $v===false ? false : true;
  }

  final public static function Safer($var) {
    //set defaults
    self::$recursionhash = array();
    self::$memorylimit = self::return_bytes(ini_get('memory_limit'));

    self::$spacing = 0;

    //echo output
    echo self::saferVarDumpObject($var);
  }  

  final private static function saferVarDumpObject($var) {
    if (!is_object($var) && !is_array($var))
      return self::Spacing().self::varDumpToString($var);

    //recursion check
    $hash = spl_object_hash($var);
    if (!empty(self::$recursionhash[$hash])) {
      return self::Spacing().'*RECURSION*'.self::Eol();
    }
    self::$recursionhash[$hash] = true;


    //create a similar output as var dump to identify the instance
    $content = self::Spacing() . self::Header($var);
    //add some spacing to mimic vardump output
    //Perhaps not the best idea because the idea is to use as little memory as possible.
    self::$spacing++;
    //Loop trough everything to output the result
    foreach($var as $k=>$v) {
      $content .= self::Spacing().self::Key($k).self::Eol().self::saferVarDumpObject($v);
    }
    self::$spacing--;
    //decrease spacing and end the object/array
    $content .= self::Spacing().self::Footer().self::Eol();
    //adding these smaller pieces to a single var works fine.
    //returning the complete larger piece gives memory error

    //length of string and the remaining memory
    $length = strlen($content);
    $left = self::$memorylimit-memory_get_usage(true);

     //enough memory left?
    if ($left>$length)
      return $content;

    //show warning  
    trigger_error('Not enough memory to dump "'.get_class($var).'" memory left:'.$left, E_USER_WARNING);
    //stop mimic output to prevent fatal memory error
    self::MimicOutput(false);
    if ($left>100) {
      return substr($content, 0, $left-100); //100 is a margin I chose, return everything you have that fits in the memory
    } else {
      return ""; //return nothing.
    }  
  }

  final private static function Spacing() {
    return self::$mimicoutput ? str_repeat(' ', self::$spacing*2) : '';
  }

  final private static function Eol() {
    return self::$mimicoutput ? PHP_EOL : '';
  }

  final private static function Header($var) {
    //the resource identifier for an object is WRONG! Its always 1 because you are passing around parts and not the actual object. Havent foundnd a fix yet
    return self::$mimicoutput ? (is_array($var) ? 'array('.count($var).')' : 'object('.get_class($var).')#'.intval($var).' ('.count((array)$var).')') . ' {'.PHP_EOL : '';
  }

  final private static function Footer() {
    return self::$mimicoutput ? '}' : '';
  }

  final private static function Key($k) {
    return self::$mimicoutput ? '['.(gettype($k)=='string' ? '"'.$k.'"' : $k ).']=>' : '';
  }

  final private static function varDumpToString($v) {
    ob_start();
    var_dump($v);

    $length = strlen($v);
    $left = self::$memorylimit-memory_get_usage(true);

     //enough memory left with some margin?
    if ($left-100>$length) {
      $content = ob_get_contents();
      ob_end_clean();
      return $content;
    }
    ob_end_clean();

    //show warning  
    trigger_error('Not enough memory to dump "'.gettype($v).'" memory left:'.$left, E_USER_WARNING);

    if ($left>100) {
      $header = gettype($v).'('.strlen($v).')';
      return $header . substr($v, $left - strlen($header));
    } else {
      return ""; //return nothing.
    }  
  }

  final private static function return_bytes($val) {
      $val = trim($val);
      $last = strtolower($val[strlen($val)-1]);
      switch($last) {
          // The 'G' modifier is available since PHP 5.1.0
          case 'g':
              $val *= 1024;
          case 'm':
              $val *= 1024;
          case 'k':
              $val *= 1024;
      }

      return $val;
  }
}
?>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...