Create a gist now

Instantly share code, notes, and snippets.

Output buffer for large output to temporary file on disk (>2MB)
<?php
/*
* Output buffer for large output to temporary file on disk (>2MB)
*
* @link http://stackoverflow.com/questions/5446647/how-can-i-use-var-dump-output-buffering-without-memory-errors/
*/
/**
* Iterate over lines of a stream resource.
*
* Note: ResourceIterator is a NoRewindIterator on non-seekable streams.
*/
class StreamIterator implements Iterator
{
private $handle;
private $current;
private $index = 0;
public function __construct($handle) {
$this->handle = $handle;
}
public function current() {
return $this->current;
}
public function next() {
$this->index++;
$this->current = fgets($this->handle);
}
public function key() {
return $this->index;
}
public function valid() {
return $this->current !== FALSE;
}
public function rewind() {
$this->index = 0;
$meta = stream_get_meta_data($this->handle);
$meta['seekable'] && fseek($this->handle, 0);
$this->next();
}
}
/**
* Interface of a Store for Buffering
*
* (subset of SplFileObject/SplTempFileObject that was originally used)
*/
interface BufferStore
{
public function fseek($offset, $whence = SEEK_SET);
public function ftruncate($size);
public function ftell();
public function fwrite($string, $length = null);
}
/**
* Stream (Stream I/O)
*/
class StreamObject implements BufferStore, IteratorAggregate
{
private $handle;
public function __construct($handle) {
$this->handle = $handle;
}
public function fseek($offset, $whence = SEEK_SET) {
fseek($this->handle, $offset, $whence);
}
public function ftruncate($size) {
ftruncate($this->handle, $size);
}
public function ftell() {
return ftell($this->handle);
}
public function fwrite($string, $length = null) {
if ($length === null) {
return fwrite($this->handle, $string);
}
return fwrite($this->handle, $string, $length);
}
public function getHandle() {
return $this->handle;
}
public function getUri() {
$meta = stream_get_meta_data($this->handle);
return $meta['uri'];
}
public function getIterator() {
return new StreamIterator($this->handle);
}
}
/**
* Uri (Stream I/O)
*/
class UriObject extends StreamObject
{
public function __construct($uri) {
parent::__construct(fopen($uri, 'r+'));
}
}
/**
* Buffer to temporary location if larger than 2 MB
*/
class OutputBuffer
{
/**
* @var int
*/
private $chunkSize;
/**
* @var bool
*/
private $started;
/**
* @var SplFileObject
*/
private $store;
/**
* @var int
*/
private $bufferedCounter;
/**
* @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 UriObject('php://temp');
}
public function start() {
if ($this->started) {
throw new BadMethodCallException('Buffering already started, can not start again.');
}
$this->started = true;
$this->bufferedCounter = 0;
$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]";
}
}
$total = $this->bufferedCounter;
file_put_contents(
'php://stderr', sprintf(
"Buffer Callback: Chunk Size %s; Total %s; Flags %s (%s); Level %d\n",
number_format($chunkSize), number_format($total + $chunkSize), $flags, $flagsText, $level
)
);
}
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);
$this->bufferedCounter += $chunkSize;
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(); # 16384
echo "Starting Buffering now.\n=======================\n";
$buffer->start();
$var = [];
foreach (range(1, 30) as $iteration) {
$array = [];
foreach (range(1, 30) as $iteration) {
$array2 = [];
foreach (range(1, 12) as $iteration) {
$array2[] = range(1, 100);
}
$array[] = $array2;
}
$var[] = $array;
}
print_r($var);
$buffer->stop();
echo "Buffering Results:\n==================\n";
$size = $buffer->getSize();
echo "Buffer Size: ", number_format($size), "\n";
echo "Memory Used: ", number_format(memory_get_usage(true)), "\n";
echo "Maximum Memory: ", ini_get('memory_limit'), "\n";
echo "String length: ", strlen($buffer), ".\n"; // this will break the 128M limit then
echo "Peeking into buffer: ", var_dump(substr($buffer, 0, 10)), ' ...', var_dump(substr($buffer, -10)), "\n";
sleep(10); // some time left to take a look into the tempdir for the buffer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment