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_replace
and calling out to MySQL withSELECT CONCAT
.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 thetmpfile
function 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.
In-Memory Streams
PHP 5.1 introduced two new in-memory streams:
php://memory
andphp://temp
. Thephp://memory
stream operates entirely in memory. Thephp://temp
stream 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://memory
stream 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.
Testing
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://memory
stream 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
$f
and 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.
Application Usage
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://temp
stream. The instance methodZend_Pdf->render()
writes the PDF output only to that stream, which is then rewound. Functions likestream_copy_to_stream
orfpassthru
can then be used to send the final output where it needs to go, and the whole process normally never needs to use the disk.
6 comments
comment by daniel 18 Oct 08
Interesting read. Could you tell a bit more about this fax integration? Do you simply send a pdf file to the machine on a network? And if so, what tools you’re using for that (some native OS commands or pure php?)? Thanks
comment by Mike Naberezny 18 Oct 08
We used interfax.net. It provides a SOAP-based web service where you can send documents for faxing and then later poll their transmission status.
comment by Jared 18 Oct 08
I’ve tried to use the memory streams in the past, together with the compression filters. But sadly it doesn’t work.
Think the compression filters require the fclose() to finalise things, by which point you’ve lost the memory stream.
comment by Dave Marshall 19 Oct 08
I’ve been using the temporary streams for a while now and particularly for testing like you pointed out, but I found something a little strange when doing so. I found that you could open php://memory in read only mode, but still are able to write to it, which scuppered some testing I was doing on my own logging class. I reported a bug, but nobody seemed interested, probably as it’s a strange use-case.
http://bugs.php.net/bug.php?id=44818
comment by Jared 20 Oct 08
Ah, this post got me thinking about it again and discovered how to get compression filters working.
Have to remove the filter from the stream before try and read the compressed contents.
comment by mvug 5 Feb 09
I get sometimes (with zend_pdf) the folowing error:
Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 1572864 bytes)
Is the temporary streams (php://temp) a way to fix this? Does anybody know if it’s possible for zend_pdf to use a tempfile instead of memory?
Sorry, the comment form is closed at this time.