Recipe 23.21. Reading and Writing Custom File Types


23.21.1. Problem

You want to use PHP's standard file access functions to provide access to data that might not be in a file. For example, you want to use file access functions to read from and write to shared memory. Or you want to process file contents when they are read before they reach PHP.

23.21.2. Solution

Write a stream wrapper that handles the details of moving data back and forth between PHP and your custom location or your custom format. A stream wrapper is a class that implements the methods that PHP needs to access your custom data stream: opening, closing, reading, writing, and so on. A particular wrapper is registered with a particular prefix. You use that prefix when passing a filename to fopen( ), include( ), or any other PHP file-handling function to ensure that your wrapper is invoked.

The PEAR Stream_SHM module implements a stream wrapper that reads from and writes to shared memory. Example 23-53 shows how to use it.

Using a custom stream wrapper

<?php require_once 'Stream/SHM.php'; stream_register_wrapper('shm','Stream_SHM') or die("can't register shm"); $shm = fopen('shm://0xabcd','c'); fwrite($shm, "Current time is: " . time()); fclose($shm); ?>

23.21.3. Discussion

Stream wrappers are handy for non-file data sources, but they can also be used to preprocess file contents on their way into PHP. Mike Naberezny demonstrates a clever example of this as applied to templating. With short_open_tags turned off, printing an object instance variable in a template requires the comparatively verbose <?php echo $this->property; ?>. Mike's solution uses a stream wrapper that allows the @ character to stand in for echo $this->. Example 23-54 shows the stream wrapper code, Example 23-55 a sample template, and Example 23-56 a short demonstration of how the two work together.

Stream wrapper for concise templates

<?php /**  * Stream wrapper to convert markup of mostly PHP templates into PHP prior to include().  *  * Based in large part on the example at  * http://www.php.net/manual/en/function.stream-wrapper-register.php  *  * @author Mike Naberezny (@link http://mikenaberezny.com)  * @author Paul M. Jones  (@link http://paul-m-jones.com)  */ class ViewStream {     /**      * Current stream position.      *      * @var int      */     private $pos = 0;     /**      * Data for streaming.      *      * @var string      */     private $data;     /**      * Stream stats.      *      * @var array      */     private $stat;     /**      * Opens the script file and converts markup.      */     public function stream_open($path, $mode, $options, &$opened_path) {         // get the view script source         $path = str_replace('view://', '', $path);         $this->data = file_get_contents($path);         /**          * If reading the file failed, update our local stat store          * to reflect the real stat of the file, then return on failure          */         if ($this->data===false) {             $this->stat = stat($path);             return false;         }         /**          * Convert <?= ?> to long-form <?php echo ?>          *          * We could also convert <%= like the real T_OPEN_TAG_WITH_ECHO          * but that's not necessary.          *          * It might be nice to also convert PHP code blocks <? ?> but          * let's quit while we're ahead.  It's probably better to keep          * the <?php for larger code blocks but that's your choice.  If          * you do go for it, explicitly check for <?xml as this will          * probably be the biggest headache.          */         if (! ini_get('short_open_tag')) {             $find = '/\<\?\= (.*)? \?>/';             $replace = "<?php echo \$1 ?>";             $this->data = preg_replace($find, $replace, $this->data);         }         /**          * Convert @$ to $this->          *          * We could make a better effort at only finding @$ between <?php ?>          * but that's probably not necessary as @$ doesn't occur much in the wild          * and there's a significant performance gain by using str_replace().          */         $this->data = str_replace('@$', '$this->', $this->data);         /**          * file_get_contents() won't update PHP's stat cache, so performing          * another stat() on it will hit the filesystem again.  Since the file          * has been successfully read, avoid this and just fake the stat          * so include() is happy.          */         $this->stat = array('mode' => 0100777,                             'size' => strlen($this->data));         return true;     }     /**      * Reads from the stream.      */     public function stream_read($count) {         $ret = substr($this->data, $this->pos, $count);         $this->pos += strlen($ret);         return $ret;     }     /**      * Tells the current position in the stream.      */     public function stream_tell() {         return $this->pos;     }     /**      * Tells if we are at the end of the stream.      */     public function stream_eof() {         return $this->pos >= strlen($this->data);     }     /**      * Stream statistics.      */     public function stream_stat() {         return $this->stat;     }     /**      * Seek to a specific point in the stream.      */     public function stream_seek($offset, $whence) {         switch ($whence) {             case SEEK_SET:                 if ($offset < strlen($this->data) && $offset >= 0) {                 $this->pos = $offset;                     return true;                 } else {                     return false;                 }                 break;             case SEEK_CUR:                 if ($offset >= 0) {                     $this->pos += $offset;                     return true;                 } else {                     return false;                 }                 break;             case SEEK_END:                 if (strlen($this->data) + $offset >= 0) {                     $this->pos = strlen($this->data) + $offset;                     return true;                 } else {                     return false;                 }                 break;             default:                 return false;         }     } } ?>

Sample template for the stream wrapper

<html> <?= @$hello ?> </html>

Demonstration of the template stream wrapper

<?php /** Stream wrapper */ require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'ViewStream.php'; /**  * A very dumb template class just to demonstrate the concept.  *  * @author Mike Naberezny  * @link   http://mikenaberezny.com/archives/40  * @link   http://phpsavant.com  */ class IdiotSavant {     public function __construct() {         if (!in_array('view', stream_get_wrappers())) {             stream_wrapper_register('view', 'ViewStream');         }     }     public function render($filename) {         include 'view://' . dirname(__FILE__) . DIRECTORY_SEPARATOR . $filename . '.phtml';     } } // Create a new view $view = new IdiotSavant(); // Assign the variable "hello" to the scope of the view $view->hello = 'Hello, World!'; // Render the view from a template.  Outputs "<html> Hello, World! </html>" $view->render('ExampleTemplate'); 

23.21.4. See Also

Documentation on stream_register_wrapper( ) at http://www.php.net/stream_register_wrapper; the PEAR Stream_SHM module at http://pear.php.net/package/stream_shm; Mike Naberezny's blog post "Symfony Templates and Ruby's ERb" at http://www.mikenaberezny.com/archives/40.




PHP Cookbook, 2nd Edition
PHP Cookbook: Solutions and Examples for PHP Programmers
ISBN: 0596101015
EAN: 2147483647
Year: 2006
Pages: 445

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net