Basic Usage
An example of the most basic usage:
<?php
use MockFileSystem\MockFileSystem as mockfs;
// Create the file system
$root = mockfs::create();
// Prefix a path with the mockfs handle
$file = mockfs::getUrl('/test');
// $file = mfs:///test
// Treat the file as any regular file
file_put_contents($file, 'Hello, World!');
chmod($file, 0600);
Creating the File System
The starting point for all mockfs usage is the mockfs::create()
method. This method allows you to set the name of the first partition, the permissions it has, and any configuration settings the file system should follow.
Note: Calling
create()
multiple times will destroy any data previously created.
Setting the Partition Name
The default mockfs partition is nameless and equates to /
as the path. However, if for some reason you want to use a different name you can:
<?php
use MockFileSystem\MockFileSystem as mockfs;
$root = mockfs::create();
// creates /
$root = mockfs::create('root');
// creates root/
$root = mockfs::create('c:');
// creates c:/
Setting the Permissions
Partitions default to using permissions 0777
(readable and writable to everyone). The create()
method allows you to override that:
<?php
use MockFileSystem\MockFileSystem as mockfs;
$root = mockfs::create('', 0750);
This can be useful if you need to make a simple “not readable” or “not writable” test:
<?php
use MockFileSystem\MockFileSystem as mockfs;
mockfs::create('', 0000);
$file = mockfs::getUrl('/test');
$handle = @fopen($file, 'w');
if ($handle === false) {
echo 'Failed to open file';
}
Setting the Configuration
mockfs defaults to using a Linux-style file system configuration. This means all of the following rules apply:
-
It uses
/
as the file separator -
Filenames are case-sensitive (e.g.
test.txt
is NOT the same asTEST.TXT
) -
Everything but
/
and thenull
character are allowed for filenames -
And
\
is not the same as/
You can change the configuration settings by either passing in an instance of MockFileSystem\Config\ConfigInterface
or a flat array
.
In this example, we tell mockfs to ignore the case of the filename. This means if you have a file named test.txt
you can access it using test.txt
, TEST.TXT
, or TesT.tXt
.
<?php
use MockFileSystem\MockFileSystem as mockfs;
$root = mockfs::create('', null, ['ignoreCase' => true]);
file_put_contents($root->getUrl().'test.txt', 'some data');
$data = file_get_contents($root->getUrl().'TeST.txt');
var_dump($data);
// outputs "some data"
Configuration Options
The following are all of the valid configuration options that can be used:
Option | Type | Description |
---|---|---|
umask |
int |
Octal representation of the umask to apply to new files added to the system. Defaults to 0000 |
fileSeparator |
string |
File separator to use. Defaults to / |
partitionSeparator |
string |
Partition separator to use (e.g. Windows would use : ). Defaults to empty string |
ignoreCase |
bool |
Whether or not to ignore filename casing (e.g. is test.txt the same as TesT.txT ). Defaults to false |
includeDotFiles |
bool |
Whether or not to include dot files (. and .. ) when listing directory contents. Defaults to true |
normalizeSlashes |
bool |
Whether or not to convert \ and / to whatever the fileSeparator option is set to. Defaults to false |
blacklist |
string[] |
Array of characters to blacklist in filenames. Can be indexed by a human-friendly name. Defaults to an empty array ([] ). Please note that the value of the fileSeparator and partitionSeparator options, as well as the null character are always in the blacklist, even when the array is empty |
user |
int|null |
The user ID of the current user. Defaults to null (gets the UID from the system) |
group |
int|null |
The group ID of the current user. Defaults to null (gets the GID from the system) |
Using Custom Configuration Presets
You can also create your own defaults by extending MockFileSystem\Config\Config::getDefaultOptions()
or creating your own MockFileSystem\Config\ConfigInterface
implementation.
For example, if you prefer to use Windows-style defaults there’s a pre-built config for that. It has the following rules:
-
It uses
\
as the file separator -
Filenames are case-insensitive (e.g.
test.txt
is the same asTEST.TXT
) -
0x00-0x1f
,0x7f
,"
,*
,/
,:
,<
,>
,?
,\
, and|
are invalid filename characters -
It does not matter if you use
\
or/
<?php
use MockFileSystem\Config\WindowsConfig;
use MockFileSystem\MockFileSystem as mockfs;
// Create the file system using Windows-style settings
mockfs::create('', null, new WindowsConfig());
Blacklisting Filename Characters
When blacklisting characters in filenames, you can use the array keys to provide a more descriptive meaning to what the character is. This is especially useful for non-printable or whitespace characters. If no string is given as the array key, it will try to display the character itself in the exception that gets thrown.
<?php
use MockFileSystem\MockFileSystem as mockfs;
$config = [
'blacklist' => [
'tab' => "\t",
'<',
'>',
'delete' => "\x7f",
],
];
mockfs::create('', null, $config);
$file = mockfs::getUrl('/in<valid');
file_put_contents($file, uniqid());
// triggers warning for 'Name cannot contain a "<" character.'
$file = mockfs::getUrl("/in\tvalid");
file_put_contents($file, uniqid());
// triggers warning for 'Name cannot contain a "tab" character.'
Setting File/Disk Quotas
You can set quotas per partition to restrict the disk space used or the number of files allowed. Each quota can be applied to a user, group, both, or none (all users).
Basic Quota
Here is a basic example in which we only allow the file system to contain one file:
<?php
use MockFileSystem\MockFileSystem as mockfs;
use MockFileSystem\Quota\Quota;
$size = Quota::UNLIMITED;
$files = 1; // Only allow one file to exist
$quota = new Quota($size, $files);
$root = mockfs::create();
$root->setQuota($quota);
// This will work
file_put_contents('mfs:///file1', uniqid());
// This will fail because only one file is allowed
file_put_contents('mfs:///file2', uniqid());
When creating a quota, there are four parameters: $size
, $fileCount
, $user
, and $group
:
Parameter | Type | Description |
---|---|---|
$size |
int |
Total number of bytes allowed to be used. Use -1 (Quota::UNLIMITED ) for unlimited bytes. |
$fileCount |
int |
Total number of files and directories allowed to be created. Use -1 (Quota::UNLIMITED ) for unlimited files. |
$user |
int|null |
The user ID to apply the quota to, or null to apply to all users. Defaults to null . |
$group |
int|null |
The group ID to apply the quota to, or null to apply to all groups. Defaults to null . |
Multiple Quotas
You can also set multiple quotas using a quota collection:
<?php
use MockFileSystem\MockFileSystem as mockfs;
use MockFileSystem\Quota\Collection;
use MockFileSystem\Quota\Quota;
$quotaA = new Quota(1024, Quota::UNLIMITED); // Only 1024 bytes allowed for all users
$quotaB = new Quota(Quota::UNLIMITED, 1, 123); // User 123 is allowed one file
$collection = new Collection([$quotaA]); // Add quotas in the constructor
$collection->addQuota($quotaB); // Or add them individually
$root = mockfs::create();
$root->setQuota($collection);
Custom Quotas
If you need to do anything more complex, you can create your own quota with whatever specific rules you need by implementing MockFileSystem\Quota\QuotaInterface
.
<?php
use MockFileSystem\MockFileSystem as mockfs;
use MockFileSystem\Quota\QuotaInterface;
class MyQuota implements QuotaInterface
{
// ...
}
$root = mockfs::create();
$root->setQuota(new MyQuota());
Testing Complex Failure
Lets say you have a class that checks for an unusual failure condition, such as a file that both exists and is readable yet somehow fails to open. Normally this would be difficult to test but it becomes easy with mockfs.
Here is our file we want to test:
<?php
namespace Acme\File;
class Processor
{
public function process(string $file)
{
if (!file_exists($file) || !is_readable($file)) {
throw new \Exception('File does not exist or is not readable');
}
$handle = @fopen($file, 'rb');
if ($handle === false) {
throw new \Exception('Failed to open file');
}
// More code...
}
}
We can test a class like this by using the stream context options that are built into mockfs. Since we can’t pass a stream context directly into the fopen()
call, we’ll have to use stream_context_set_default()
. Here we want fopen()
to fail so that is the context option we will set.
We’ll use PHPUnit to test this example, but the same concept applies to any other testing framework.
<?php
namespace Acme\Tests\File;
use Acme\File\Processor;
use MockFileSystem\MockFileSystem as mockfs;
use PHPUnit\Framework\TestCase;
class ProcessorTest extends TestCase
{
/**
* @var Processor
*/
private $fixture = null;
protected function setUp(): void
{
mockfs::create();
$this->fixture = new Processor();
}
protected function tearDown(): void
{
// Unset the option we're going to use in our test
stream_context_set_default(
[
'mfs' => [
'fopen_fail' => false,
]
]
);
}
public function testProcessFailsToOpenFile(): void
{
$file = mockfs::getUrl('/example');
file_put_contents($file, uniqid());
$this->assertTrue(file_exists($file));
$this->assertTrue(is_readable($file));
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Failed to open file');
// Use stream context to tell mockfs to fail on fopen()
stream_context_set_default(
[
// "mfs" is the mockfs stream wrapper
'mfs' => [
'fopen_fail' => true,
]
]
);
$this->fixture->process($file);
}
}
Be careful about where you place the stream_context_set_default()
call. If you set it too early, you may cause file operations such as file_put_contents()
to fail. Try not to set it until just before your expected failure.
Also be sure to unset any context options in the tearDown()
method or tag the test as @runInSeparateProcess
to isolate it. If you don’t, you’ll risk other tests being affected.
Supported Options
The following context options are available to force different types of failures. You can set any combination of them at the same time. With all the options available, this allows you to get extremely granular in your tests.
Directory Operations
Option | Type | Description |
---|---|---|
opendir_fail |
bool |
Force calls to opendir() to fail |
opendir_message |
string|null |
The user warning to trigger on failure. This has no effect unless opendir_fail is set to true |
closedir_fail |
bool |
Force calls to closedir() to fail. Please note that even when this fails, PHP still destroys the stream handle |
readdir_fail |
bool |
Force calls to readdir() to fail |
rewinddir_fail |
bool |
Force calls to rewinddir() to fail. Please note that even when this fails, PHP still reports it as a success |
mkdir_fail |
bool |
Force calls to mkdir() to fail |
mkdir_message |
string|null |
The user warning to trigger on failure. This has no effect unless mkdir_fail is set to true |
rmdir_fail |
bool |
Force calls to rmdir() to fail |
rmdir_message |
string|null |
The user warning to trigger on failure. This has no effect unless rmdir_fail is set to true |
File Operations
Option | Type | Description |
---|---|---|
fopen_fail |
bool |
Force calls to fopen() , file_get_contents() , or file_put_contents() to fail |
fopen_message |
string|null |
The user warning to trigger on failure. This has no effect unless fopen_fail is set to true |
fclose_fail |
bool |
Force calls to fclose() to fail. Please note that PHP does not report this failure |
fread_fail |
bool |
Force calls to fread() , fgets() , fgetcsv() , and similar to fail |
fwrite_fail |
bool |
Force calls to fwrite() to fail |
fseek_fail |
bool |
Force calls to fseek() to fail |
ftell_fail |
bool |
Force calls to ftell() to fail. Please note that PHP does not correctly report the stream wrapper failure and will return 0 instead |
feof_fail |
bool |
Force calls to feof() to fail |
feof_response |
bool |
Response to return for feof() . Defaults to false |
fflush_fail |
bool |
Force calls to fflush() to fail |
fstat_fail |
bool |
Force calls to fstat() to fail |
ftruncate_fail |
bool |
Force calls to ftruncate() to fail |
rename_fail |
bool |
Force calls to rename() to fail |
rename_message |
string|null |
The user warning to trigger on failure. This has no effect unless rename_fail is set to true |
stat_fail |
bool |
Force calls to stat() to fail |
stat_message |
string|null |
The user warning to trigger on failure. This has no effect unless stat_fail is set to true |
touch_fail |
bool |
Force calls to touch() to fail |
touch_message |
string|null |
The user warning to trigger on failure. This has no effect unless touch_fail is set to true |
unlink_fail |
bool |
Force calls to unlink() to fail |
unlink_message |
string|null |
The user warning to trigger on failure. This has no effect unless unlink_fail is set to true |
Browsing the File System
If for any reason you need to view/browse the file system, you can call mockfs::visit()
. It defaults to using a MockFileSystem\Visitor\TreeVisitor
to print the file system contents to STDOUT
.
<?php
mockfs::create('/', null ['bar' => ['baz' => ''], 'foo' => '']);
mockfs::visit();
// mfs://
// └── /
// ├── bar
// │ └── baz
// └── foo
If you only want to browse part of the file system, you can pass in a file as the first parameter:
<?php
mockfs::create('/', null ['bar' => ['baz' => ''], 'foo' => '']);
$file = mockfs::find('/bar');
mockfs::visit($file);
// /bar
// └── baz
If you want to do something other than print the tree to the screen, you can create a custom visitor by implementing MockFileSystem\Visitor\VisitorInterface
and pass that in as the second parameter.
<?php
use MockFileSystem\Visitor\VisitorInterface;
mockfs::create('/', null ['bar' => ['baz' => ''], 'foo' => '']);
/** @var VisitorInterface $visitor */
$visitor = ...;
mockfs::visit(null, $visitor);