42 votes

Comment utiliser var_dump + la mise en tampon de sortie sans erreur de mémoire?

Je suis à l'aide d'un débogage de l'aide dans une application qui utilise var_dump() avec le tampon de sortie de la capture de variables et de les afficher. Cependant, je suis en cours d'exécution dans un problème avec de grands objets qui utilisent trop de mémoire dans la mémoire tampon.

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
}

Est il possible que je peux faire pour éviter cela? Ou un travail autour de détecter qu'il est sur le point de sortie d'une quantité gigantesque de l'info, pour une variable donnée? Je n'ai pas vraiment de contrôle les variables qui se passait dans cette fonction. Il pourrait être n'importe quel type.

21voto

Hugo Delsing Points 7530

Comme tous les autres sont en mentionnant ce que vous demandez est impossible. La seule chose que vous pouvez faire est d'essayer de le manipuler comme bon que possible.

Ce que vous pouvez faire est d'essayer de la découper en petits morceaux, puis mélanger. J'ai créé un petit test pour essayer et obtenir de l'erreur de mémoire. Évidemment un exemple réel peuvent se comporter différemment, mais ce qui semble faire l'affaire.

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

Mise à JOUR La version ci-dessus a encore quelques erreurs. J'ai recréé de manière à utiliser une classe et quelques autres fonctions

  • De vérifier la récursivité
  • Correctif pour seul grand attribut
  • Imiter var_dump de sortie
  • trigger_error sur alerte pour être capable d'attraper/masquer

Comme indiqué dans les commentaires, l'identificateur de ressource pour une classe est différente à partir de la sortie de var_dump. Aussi loin que je peux dire d'autres choses sont égales.

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

17voto

hakre Points 102271

Eh bien, si la mémoire physique est limitée (vous voyez l'erreur fatale:)

Fatal error: Allowed memory size of 536 870 912 octets épuisé

Je suggère de faire le tampon de sortie sur le disque (voir rappel paramètre ob_start). Le tampon de sortie fonctionne en bloc, ce qui signifie que si il y a encore assez de mémoire pour conserver le seul morceau dans la mémoire, vous pouvez le stocker dans un fichier temporaire.

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

Toutefois, vous devez garder à l'esprit que ce sera seulement de prévenir la fatale erreur lors de la mise en mémoire tampon. Maintenant, si vous voulez retourner la mémoire tampon, vous avez encore besoin d'avoir assez de mémoire ou de vous retourner le descripteur de fichier ou fichier-chemin de sorte que vous pouvez également diffuser de la sortie.

Toutefois, vous pouvez utiliser ce fichier pour obtenir la taille en octets nécessaires. Les frais généraux pour les chaînes de PHP n'est pas beaucoup d'autant que je me souvienne, donc si il y a encore suffisamment de mémoire libre pour la taille cela devrait bien fonctionner. Vous pouvez soustraire l'offset d'avoir un peu de place et de jouer en toute sécurité. Juste essayer et d'erreur un peu ce qu'il fait.

Un Exemple de code (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";

Sortie:

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"

13voto

Jan. Points 1088

Lorsque vous insérez xdebug, vous pouvez limiter la profondeur de var_dump après les objets. Dans certains produits logiciels, vous pouvez rencontrer une sorte de récursivité qui gêne la sortie de var_dump. En dehors de cela, vous pouvez augmenter la limite de mémoire.

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

10voto

Mario Mueller Points 773

Je suis désolé, mais je pense qu'il n'y a pas de solution à votre problème. Vous vous demandez pour la détermination de la taille pour empêcher l'allocation de mémoire pour cette taille. PHP ne peut pas vous donner une réponse sur "la quantité de mémoire qu'il consomme", comme la ZVAL les structures sont créées au moment de l'utilisation de PHP. Veuillez vous référer à la Programmation PHP - 14.5. Gestion de la mémoire pour un aperçu de mémoire PHP répartition des éléments internes.

Vous avez donné le bon conseil "il peut y avoir quoi que ce soit" et c'est le problème de mon point de vue. Il y a un problème architectural qui mène le cas que vous décrivez. Et je pense que vous essayez de le résoudre sur le mauvais bout.

Par exemple: vous pouvez commencer avec un commutateur pour chaque type en php et essayer de fixer des limites pour chaque taille. Cela dure aussi longtemps que personne ne vient à l'idée de changer la limite de la mémoire dans le processus.

Xdebug est une bonne solution qu'il vous garde en application de l'explosion à cause d'une (même non critique) la fonction log et c'est une mauvaise solution que vous devez pas activer l'extension xdebug dans la production.

Je pense qu'un souvenir d'exception est le comportement correct et vous ne devriez pas essayer de le contourner.

[coup de gueule]Si celui qui les décharges de 50 méga-octets ou plus de corde ne se soucie pas de son app comportement, il/elle mérite d'en souffrir ;)[/coup de gueule]

6voto

JMack Points 1829

Je ne crois pas qu'il y est de toute façon de déterminer la quantité de mémoire d'une fonction spécifique va finir par prendre. Une chose que vous pouvez faire est d'utiliser la fonction memory_get_usage() pour vérifier la quantité de mémoire que le script est actuellement en train de prendre à droite avant d' $largeVar est de définir, puis de le comparer avec le montant après. Cela vous donnera une bonne idée de la taille de l' $largeVar, et vous pouvez effectuer des essais pour déterminer ce qu'un maximum acceptable limite de taille avant de vous quitter en douceur.

Vous pouvez également remettre sur pied le var_dump() de la fonction de vous-même. La fonction de marche à travers la structure et de l'écho le contenu résultant comme il est généré, ou les stocker dans un fichier temporaire, plutôt que de stocker une gigantesque chaîne de caractères en mémoire. Cela vous permettra d'obtenir le même résultat souhaité, mais sans la mémoire, des problèmes que vous rencontrez.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X