kaiser kaiser - 6 months ago 8
PHP Question

How to create a fake / "virtual" file?

I am trying to create a "virtual" file without using either memory or a temporary file. The "virtual" file needs to pass a check made with

file_exists()
while not throwing any error or warning when used with
require
or
include
.


Allows you to implement your own protocol handlers and streams for use with all the other filesystem functions (such as
fopen()
,
fread()
etc.).


…where
file_exists()
is one of them. The docs page states:


As of PHP 5.0.0, this function can also be used with some URL wrappers. Refer to Supported Protocols and Wrappers to determine which wrappers support
stat()
family of functionality.


My attempt there was to build a custom, virtual file wrapper

class VirtualFileWrapper
{
public $context;

public function stream_open( $path, $mode, $options, &$opened_path )
{
return TRUE;
}

public function stream_stat()
{
var_dump( __METHOD__ );
return [];
}
}

stream_wrapper_register( 'virtual', 'VirtualFileWrapper' );

$file = fopen( "virtual://foo", 'r+' );

// Executes VirtualFileWrapper::stream_stat()
fstat( $file );

// Executes no VirtualFileWrapper method
file_exists( $file );


While the
fstat()
function executes the method,
file_exists()
does not execute any stream class method.

How could I get a virtual stream wrapper to work (with
file_exists()
)?




I am fully aware that
tempnam( __DIR__, '' )
will pass both:


  • var_dump( tempnam( __DIR__, '' ) );
    Returns
    true

  • require tempnam( __DIR__, '' );
    No error



but I do not want to use a temporary file as there might be a better way (performance wise).

Answer

It looks like you just need to implement a public url_stat() method on VirtualFileWrapper for it to pass the file_exists() check.

To silence warnings and errors from include and require, you must implement stream_read() and stream_eof() methods:

class VirtualFileWrapper
{
    public $context;

    public function stream_open( $path, $mode, $options, &$opened_path )
    {
        return TRUE;
    }

    public function stream_stat()
    {
        var_dump( __METHOD__ );
        return [];
    }

    public function url_stat()
    {
        return array (
          0 => 0,
          1 => 0,
          2 => 0,
          3 => 0,
          4 => 0,
          5 => 0,
          6 => 0,
          7 => 0,
          8 => 0,
          9 => 0,
          10 => 0,
          11 => 0,
          12 => 0,
          'dev' => 0,
          'ino' => 0,
          'mode' => 0,
          'nlink' => 0,
          'uid' => 0,
          'gid' => 0,
          'rdev' => 0,
          'size' => 0,
          'atime' => 0,
          'mtime' => 0,
          'ctime' => 0,
          'blksize' => 0,
          'blocks' => 0
        );
    }

    public function stream_read(){
        return '';
    }

    public function stream_eof(){
        return '';
    }

}

stream_wrapper_register( 'virtual', 'VirtualFileWrapper' );

$file = fopen( "virtual://foo", 'r+' );

// Executes VirtualFileWrapper::stream_stat()
fstat( $file );

// Executes no VirtualFileWrapper method
file_exists("virtual://foo");

//Still no errors :-)!
require "virtual://foo";
include "virtual://foo";

Take care to pass file_exists() a string, rather than the resource you created with fopen().