2011-03-27 15 views
38

मैं उत्पादन बफरिंग के साथ var_dump() का उपयोग करता है चर और उन्हें प्रदर्शित करने के लिए एक आवेदन पत्र में एक डिबगिंग सहायता का उपयोग कर रहा हूँ। हालांकि, मैं बड़ी वस्तुओं के साथ एक समस्या में भाग रहा हूं जो बफर में बहुत अधिक स्मृति का उपयोग कर समाप्त होता है।मैं स्मृति त्रुटियों के बिना var_dump + आउटपुट बफरिंग का उपयोग कैसे कर सकता हूं?

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 
} 

वहाँ एक रास्ता है कि मैं इस को रोका जा सकता है? या यह पता लगाने के लिए एक कार्य-आसपास है कि यह किसी विशेष चर के लिए बड़ी मात्रा में जानकारी आउटपुट करने वाला है? मेरे पास वास्तव में नियंत्रण नहीं है कि इस चर में कौन से चर पारित हो जाते हैं। यह किसी भी प्रकार का हो सकता है।

+0

आप जिज्ञासा से बाहर 'print_r', साथ ही समस्या मिलता है? यदि नहीं, तो क्या आप बहुत सारे रिकर्सन नोटिस देखते हैं? – Charles

+0

@ चार्ल्स, शायद नहीं। मैं * 'print_r' या' var_export' का उपयोग कर सकता हूं लेकिन मुझे वास्तव में यह तथ्य पसंद है कि मैं 'var_dump' द्वारा प्रदान किए गए चर प्रकार और लंबाई की जानकारी को बरकरार रख सकता हूं। Xdebug उपलब्ध होने पर भी अतिरिक्त स्वरूपण लाभ। –

+0

रिकर्सन के कारण शायद यह आउटपुट की असीमित मात्रा है। क्या होता है यह देखने के लिए आउटपुट बफरिंग का उपयोग किये बिना इसे स्वयं को एक ही var पर कॉल करने का प्रयास करें। – Jon

उत्तर

16

ठीक है, यदि भौतिक स्मृति सीमित है (आप गंभीर त्रुटि :)

Fatal error: Allowed memory size of 536870912 bytes exhausted

मैं डिस्क पर उत्पादन बफरिंग करने के लिए (ob_start पर कॉलबैक पैरामीटर देखें) सुझाव है कि देखते हैं। आउटपुट बफरिंग कामों को खंडित किया गया है, इसका मतलब है, अगर स्मृति में एकल खंड को रखने के लिए अभी भी पर्याप्त स्मृति है, तो आप इसे एक अस्थायी फ़ाइल में संग्रहीत कर सकते हैं।

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

हालांकि आपको यह ध्यान में रखना चाहिए कि यह केवल बफरिंग के दौरान घातक त्रुटि को रोक देगा। अगर आप अब बफर वापस करना चाहते हैं, तो आपको अभी भी पर्याप्त मेमोरी या की आवश्यकता है, आप फ़ाइल-हैंडल या फ़ाइल-पथ वापस कर सकते हैं ताकि आप आउटपुट स्ट्रीम भी कर सकें।

हालांकि आप उस फ़ाइल का उपयोग तब बाइट्स में आकार प्राप्त करने के लिए कर सकते हैं। PHP स्ट्रिंग्स के लिए ओवरहेड ज्यादा आईआईआरसी नहीं है, इसलिए यदि फाइलसाइज के लिए अभी भी पर्याप्त मेमोरी है तो इसे अच्छी तरह से काम करना चाहिए। आप ऑफसेट को थोड़ा कमरा रखने और सुरक्षित खेलने के लिए निकाल सकते हैं। बस इसे थोड़ा सा प्रयास करें और त्रुटि दें।

कुछ उदाहरण कोड (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" 
+0

आप परिणामी फ़ाइल को केवल 'fpassthru' कर सकते हैं, जो चंकित पढ़/लिखने/फ्लश करेगा, और स्मृति का एक टन नहीं लेता है। – Leigh

+2

@Leigh: हाँ, यह संभव होगा। अब मैंने अवधारणा का सबूत किया है, फाइल-हैंडल का उपयोग नहीं बल्कि एक 'SplTempFileObject'। तकनीकी रूप से एक 'tmp: // 'समान रूप से संभव होगा, शायद बेहतर भी। वैसे भी अवधारणा के सबूत के लिए यह काम करता है। 'SplTempFileObject' और' tmp: // 'आंशिक रूप से पहले स्मृति में स्ट्रीम करने की अनुमति देता है और यदि अधिक स्मृति का उपयोग किया जाता है तो वे इसे डिस्क पर डाल देते हैं। शायद यह सबसे ज्यादा वांछित है। जैसे 1 एमबी तक या स्मृति में समान डंप को रखते हुए डिस्क पर बड़े होते हैं। – hakre

+1

मैंने अब कुछ और परीक्षण किया है। Xdebug सक्षम होने पर इसे 'var_dump' के साथ काम करने की अपेक्षा न करें। यह काम नहीं करता है क्योंकि Xdebug पूरे 'var_dump' आउटपुट को एक बार में भेजने के कारण बनता है और भाग में नहीं, क्योंकि यह सामान्य है (देखें: http://codepad.viper-7.com/PVI5qT - @DaveRandom द्वारा परीक्षण)। कुछ बदल गए कोड और परीक्षण यहां है: https://gist.github.com/4341870 - मूल विचार अभी भी वही है, मैं अभी तक प्रश्न को संपादित नहीं करना चाहता हूं। – hakre

13

जब आप xdebug को अनदेखा करते हैं तो आप सीमित कर सकते हैं कि var_dump वस्तुओं का पालन कैसे करता है। कुछ सॉफ़्टवेयर उत्पादों में आपको एक प्रकार का रिकर्सन मिल सकता है, जो var_dump के आउटपुट को फॉलो करता है। इसके अलावा, आप स्मृति सीमा बढ़ा सकते हैं।

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

+3

मैं पोस्ट की सराहना करता हूं, लेकिन जवाब देता हूं कि "इसे कम करें" कम करें, वास्तव में केवल लक्षण से निपटें, समस्या नहीं। इस फ़ंक्शन में 10-आयामी सरणी हो सकती है जिसमें प्रत्येक में केवल एक तत्व होता है - जो डंप करने के लिए ठीक होना चाहिए। लेकिन अन्य विशाल वस्तुओं के गुणों वाले वर्ग दूसरे स्तर पर समस्याएं पैदा कर सकते हैं। मुझे एहसास है कि मैं जो पूछ रहा हूं वह असंभव हो सकता है –

+1

उचित रूप से यह सिर्फ एक बात है कि आपके पास कितनी भौतिक स्मृति है। =) –

9

मैं माफी चाहता हूँ देखें, लेकिन मुझे लगता है कि आपकी समस्या के लिए कोई समाधान नहीं है। आप उस आकार के लिए स्मृति आवंटन को रोकने के लिए आकार के निर्धारण के लिए पूछ रहे हैं। PHP आपको "कितनी मेमोरी का उपभोग करेगा" के बारे में उत्तर नहीं दे सकता है, क्योंकि PHP में उपयोग के समय ZVAL structs बनाए जाते हैं। PHP के मेमोरी आवंटन आंतरिक के अवलोकन के लिए कृपया Programming PHP - 14.5. Memory Management देखें।

आपने सही संकेत दिया "इसमें कुछ भी हो सकता है" और यह मेरे दृष्टिकोण से समस्या है। एक वास्तुशिल्प समस्या है जो आपके द्वारा वर्णित मामले की ओर ले जाती है। और मुझे लगता है कि आप इसे गलत अंत में हल करने का प्रयास करते हैं।

उदाहरण के लिए: आप प्रत्येक प्रकार के PHP में स्विच के साथ शुरू कर सकते हैं और प्रत्येक आकार के लिए सीमा निर्धारित करने का प्रयास कर सकते हैं। यह तब तक चलता है जब तक प्रक्रिया में स्मृति सीमा को बदलने के विचार में कोई भी नहीं आता है।

Xdebug एक अच्छा समाधान है क्योंकि यह आपको (यहां तक ​​कि गैर-व्यवसाय-महत्वपूर्ण) लॉग फ़ंक्शन के कारण विस्फोट से एप्लिकेशन रखता है और यह एक खराब समाधान है क्योंकि आपको उत्पादन में xdebug सक्रिय नहीं करना चाहिए।

मुझे लगता है कि एक स्मृति अपवाद सही व्यवहार है और आपको इसके आसपास काम करने की कोशिश नहीं करनी चाहिए।

[शेख़ी] एक है जो एक 50 मेगाबाइट या अधिक स्ट्रिंग उदासीनता उसकी/उसके एप्लिकेशन व्यवहार के बारे में परवाह नहीं है, वह/वह यह से पीड़ित होने के हकदार हैं, [/ शेख़ी])

+0

यह सही उत्तर होना चाहिए। यदि यह संभव नहीं है तो आपके दृष्टिकोण में कुछ गड़बड़ है ... –

5

मैं नहीं मानता यह निर्धारित करने का कोई तरीका है कि एक विशिष्ट कार्य अंततः कितनी मेमोरी लेगा। एक बात आप कर सकते हैं memory_get_usage() का उपयोग कितनी स्मृति स्क्रिप्ट वर्तमान में ले जा रहा है ठीक पहले $largeVar सेट किया गया है की जाँच करने के लिए, तो राशि के बाद से इसकी तुलना है। यह आपको $largeVar के आकार का एक अच्छा विचार देगा, और आप यह निर्धारित करने के लिए परीक्षण चला सकते हैं कि आप स्वीकार्य रूप से बाहर निकलने से पहले अधिकतम स्वीकार्य आकार सीमा क्या होगी।

तुम भी var_dump reimplement सकता है() अपने आप कार्य करते हैं। फ़ंक्शन को संरचना के माध्यम से चलें और परिणामी सामग्री को उत्पन्न होने के रूप में गूंजें, या इसे एक अस्थायी रूप में संग्रहीत करें, बल्कि स्मृति में एक विशाल स्ट्रिंग संग्रहित करें। यह आपको वही वांछित परिणाम प्राप्त करने की अनुमति देगा, लेकिन स्मृति समस्याओं के बिना आप सामना कर रहे हैं।

20

के रूप में अन्य सभी उल्लेख कर रहे हैं कि तुम क्या पूछना असंभव है। एकमात्र चीज जो आप कर सकते हैं उसे जितना संभव हो उतना अच्छा संभालने का प्रयास करें।

आप इसे आजमा सकते हैं कि इसे छोटे टुकड़ों में विभाजित करें और फिर इसे गठबंधन करें। मैंने स्मृति त्रुटि को आज़माने और प्राप्त करने के लिए एक छोटा परीक्षण बनाया है।जाहिर है कि एक असली दुनिया का उदाहरण अलग-अलग व्यवहार कर सकता है, लेकिन ऐसा लगता है कि यह चाल है।

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

अद्यतन संस्करण ऊपर अभी भी कुछ त्रुटि है। मैं एक वर्ग और कुछ अन्य कार्यों का उपयोग करने के लिए इसे निर्मित प्रत्यावर्तन के लिए

  • चेक
  • एक बड़ी विशेषता
  • नकल 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; 
    } 
} 
?> 
+0

दिलचस्प। यदि कोई वस्तु संपत्ति अपमानजनक रूप से बड़ी थी, तो यह कैसे व्यवहार करेगा? – Charles

+0

कोशिश की और असफल :) काम करने के लिए एक और व्यापक संस्करण के साथ अद्यतन किया गया। –

संबंधित मुद्दे