PHP Temporary Streams
It’s been a while since David Sklar called out to let a thousand string concatenations bloom. That discussion produced some entertaining suggestions for putting strings together such as using
preg_replaceand calling out to MySQL with
Here’s an approach that uses filesystem functions. When combined with some lesser-known PHP streams functionality, it has several practical applications.
Opening Files for Reading and Writing
There are several modes for opening a file that will allow you to seek to arbitrary positions in the file and read or write at those positions. One of the most frequently used of these modes is
w+, which the
tmpfilefunction uses automatically.
We can repeatedly write, then rewind the pointer and read:
$f = tmpfile(); fwrite($f, 'foo'); fwrite($f, 'bar'); rewind($f); $contents = stream_get_contents($f); //=> "foobar" fclose($f);
When writing to the filesystem, the above provides yet another inefficient solution to David’s exercise. Now let’s take it a bit further to see how this can be useful.
PHP 5.1 introduced two new in-memory streams:
php://memorystream operates entirely in memory. The
php://tempstream operates in memory until it reaches a given size, then transparently switches to the filesystem.
We can modify the above example to use the
php://memorystream instead of hitting the filesystem:
$f = fopen('php://memory', 'w+'); fwrite($f, 'foo'); fwrite($f, 'bar'); rewind($f); $contents = stream_get_contents($f); //=> "foobar" fclose($f);
Putting a string inside a fast temporary stream can be very useful. For example, we can then attach filters to that stream.
Temporary streams are also handy for testing. There are some rather elaborate virtual file system libraries out there but many times a stream is all you need.
Zend_Log has a log handler for streams that accepts either a filename or a stream resource. We can configure it with a
php://memorystream for testing:
$f = fopen('php://memory', 'w+'); $writer = new Zend_Log_Writer_Stream($f); $logger = new Zend_Log($writer);
Assuming your well-designed application has a convenient injection point for the logger instance, your test can pass it in before your test activates some action which should result in logging:
$logger->crit('critical message worth testing');
When the action has completed, the test can rewind
$fand use it as a test spy.
rewind($f); $contents = stream_get_contents($f); $this->assertRegExp('/message worth testing/i', $contents); // PHPUnit
Not surprisingly, my unit tests for Zend_Log use this same technique.
Not long ago, we built a custom document storage system for a client. One of its more interesting features is that it integrates with an internet fax service so users can select documents in the system and then fax them. For each fax, the software automatically generates a cover page.
To make the cover page, I first made a nice template using a drawing tool and then saved it in PDF format. I then used Zend_Pdf to write over the template with dynamic content. I first demonstrated this technique in this article.
Since the cover page is only used once (during transmission) and easy to regenerate, I don’t save the output to the filesystem. Instead, I create a
php://tempstream. The instance method
Zend_Pdf->render()writes the PDF output only to that stream, which is then rewound. Functions like
fpassthrucan then be used to send the final output where it needs to go, and the whole process normally never needs to use the disk.
Sorry, the comment form is closed at this time.