我怎样才能使用var_dump +输出缓冲没有内存错误?

我在使用var_dump()和输出缓冲来捕获variables并显示它们的应用程序中使用了一个debugging帮助。 但是,我遇到了一个大对象的问题,最终在缓冲区中占用了太多的内存。

 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 } 

有没有办法阻止这种情况? 还是解决方法来检测它即将输出一个特定variables的巨大数量的信息? 我真的不能控制哪些variables被传入这个函数。 它可以是任何types。

那么,如果物理内存是有限的(你看到致命的错误:)

致命错误:允许内存大小536870912字节用尽

我会build议做磁盘上的输出缓冲(请参阅ob_start上的callback参数)。 输出缓冲工作分块,这意味着,如果仍然有足够的内存来保持单个块在内存中,则可以将其存储到临时文件中。

 // handle output buffering via callback, set chunksize to one kilobyte ob_start($output_callback, $chunk_size = 1024); 

但是,您必须记住,这只会防止缓冲时的致命错误。 如果您现在想要返回缓冲区,则仍然需要有足够的内存, 或者返回文件句柄或文件path,以便还可以输出stream。

但是,您可以使用该文件,然后获取所需的字节大小。 PHPstring的开销并不多,所以如果文件大小仍然有足够的内存空间,这应该很好。 你可以减去抵消,有一个小房间,玩安全。 只是尝试和错误一点点。

一些示例代码(PHP 5.4):

 <?php /** * @link http://stackoverflow.com/questions/5446647/how-can-i-use-var-dump-output-buffering-without-memory-errors/ */ class OutputBuffer { /** * @var int */ private $chunkSize; /** * @var bool */ private $started; /** * @var SplFileObject */ private $store; /** * @var bool Set Verbosity to true to output analysis data to stderr */ private $verbose = true; public function __construct($chunkSize = 1024) { $this->chunkSize = $chunkSize; $this->store = new SplTempFileObject(); } public function start() { if ($this->started) { throw new BadMethodCallException('Buffering already started, can not start again.'); } $this->started = true; $result = ob_start(array($this, 'bufferCallback'), $this->chunkSize); $this->verbose && file_put_contents('php://stderr', sprintf("Starting Buffering: %d; Level %d\n", $result, ob_get_level())); return $result; } public function flush() { $this->started && ob_flush(); } public function stop() { if ($this->started) { ob_flush(); $result = ob_end_flush(); $this->started = false; $this->verbose && file_put_contents('php://stderr', sprintf("Buffering stopped: %d; Level %d\n", $result, ob_get_level())); } } private function bufferCallback($chunk, $flags) { $chunkSize = strlen($chunk); if ($this->verbose) { $level = ob_get_level(); $constants = ['PHP_OUTPUT_HANDLER_START', 'PHP_OUTPUT_HANDLER_WRITE', 'PHP_OUTPUT_HANDLER_FLUSH', 'PHP_OUTPUT_HANDLER_CLEAN', 'PHP_OUTPUT_HANDLER_FINAL']; $flagsText = ''; foreach ($constants as $i => $constant) { if ($flags & ($value = constant($constant)) || $value == $flags) { $flagsText .= (strlen($flagsText) ? ' | ' : '') . $constant . "[$value]"; } } file_put_contents('php://stderr', "Buffer Callback: Chunk Size $chunkSize; Flags $flags ($flagsText); Level $level\n"); } if ($flags & PHP_OUTPUT_HANDLER_FINAL) { return TRUE; } if ($flags & PHP_OUTPUT_HANDLER_START) { $this->store->fseek(0, SEEK_END); } $chunkSize && $this->store->fwrite($chunk); if ($flags & PHP_OUTPUT_HANDLER_FLUSH) { // there is nothing to d } if ($flags & PHP_OUTPUT_HANDLER_CLEAN) { $this->store->ftruncate(0); } return ""; } public function getSize() { $this->store->fseek(0, SEEK_END); return $this->store->ftell(); } public function getBufferFile() { return $this->store; } public function getBuffer() { $array = iterator_to_array($this->store); return implode('', $array); } public function __toString() { return $this->getBuffer(); } public function endClean() { return ob_end_clean(); } } $buffer = new OutputBuffer(); echo "Starting Buffering now.\n=======================\n"; $buffer->start(); foreach (range(1, 10) as $iteration) { $string = "fill{$iteration}"; echo str_repeat($string, 100), "\n"; } $buffer->stop(); echo "Buffering Results:\n==================\n"; $size = $buffer->getSize(); echo "Buffer Size: $size (string length: ", strlen($buffer), ").\n"; echo "Peeking into buffer: ", var_dump(substr($buffer, 0, 10)), ' ...', var_dump(substr($buffer, -10)), "\n"; 

输出:

 STDERR: Starting Buffering: 1; Level 1 STDERR: Buffer Callback: Chunk Size 1502; Flags 1 (PHP_OUTPUT_HANDLER_START[1]); Level 1 STDERR: Buffer Callback: Chunk Size 1503; Flags 0 (PHP_OUTPUT_HANDLER_WRITE[0]); Level 1 STDERR: Buffer Callback: Chunk Size 1503; Flags 0 (PHP_OUTPUT_HANDLER_WRITE[0]); Level 1 STDERR: Buffer Callback: Chunk Size 602; Flags 4 (PHP_OUTPUT_HANDLER_FLUSH[4]); Level 1 STDERR: Buffer Callback: Chunk Size 0; Flags 8 (PHP_OUTPUT_HANDLER_FINAL[8]); Level 1 STDERR: Buffering stopped: 1; Level 0 Starting Buffering now. ======================= Buffering Results: ================== Buffer Size: 5110 (string length: 5110). Peeking into buffer: string(10) "fill1fill1" ...string(10) "l10fill10\n" 

所有其他人都提到你所问的是不可能的。 你唯一能做的就是尽可能地处理它。

你可以尝试的是把它分成小块,然后把它合并起来。 我已经创build了一个小testing来获取内存错误。 很显然,一个真实世界的例子可能会有不同的performance,但这似乎是个窍门。

 <?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; } ?> 

更新上面的版本仍然有一些错误。 我重新创build它使用一个类和一些其他function

  • 检查recursion
  • 修复单个大型属性
  • 模仿var_dump输出
  • 在警告trigger_error可以捕捉/隐藏它

如注释所示,类的资源标识符与var_dump的输出不同。 据我所知,其他的东西是平等的。

 <?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; } } ?> 

当你安装xdebug时,你可以限制var_dump跟在对象后面的深度。 在某些软件产品中,您可能会遇到一种recursion,这会增加var_dump的输出。 除此之外,你可以提高内存限制。

http://www.xdebug.org/docs/display

我很抱歉,但我认为你的问题没有解决办法。 您正在要求确定大小以防止为该大小分配内存。 PHP不能给你一个关于“消耗多less内存”的答案,因为ZVAL结构是在PHP中使用的时候创build的。 请参考PHP-14.5编程。 内存pipe理概述PHP的内存分配内部。

你给出了正确的提示“里面可以有任何东西”,从我的angular度来看,这是个问题。 有一个build筑问题导致你描述的情况。 而且我认为你试图在错误的方面解决它。

例如:你可以在php中为每种types开关,并尝试设置每个大小的限制。 只要没有人想到在这个过程中改变内存限制,这个过程就会持续下去。

Xdebug是一个很好的解决scheme,因为它使得应用程序不会因为(甚至是非业务关键的)日志function而爆炸,而且这是一个不好的解决scheme,因为您不应该在生产环境中激活xdebug。

我认为,内存exception是正确的行为,你不应该试图解决它。

[咆哮]如果倾倒50兆字节或更多的string的人不关心他/她的应用程序行为,他/她应该受到影响;)[/ rant]

我不相信有什么办法可以确定一个特定function将最终占用多less内存。 你可以做的一件事是使用memory_get_usage()来检查脚本在$largeVar被设置之前正在执行的内存的多less,然后将它与之后的数量进行比较。 这将给你一个$largeVar的大小的好主意,你可以运行试验来确定在你正常退出之前什么是最大可接受的大小限制。

你也可以自己重新实现var_dump()函数。 使函数遍历结构并在生成时回应生成的内容,或者将其存储在临时文件中,而不是在内存中存储巨大的string。 这将允许你得到相同的期望的结果,但没有你遇到的内存问题。