Skip to main content

Null File

This chapter describes the null object pattern applied to files.

The Missing File Problem

One of the most common problems when working with files is how to handle the case when the business logic necessitates that a file must be present, but in actuality, it is not. For example, if an image has been assigned to a product in the past, the product has a record of it and knows that it has an image. But in the storage, the image is missing for some reason.

We can use a standard null value, but by doing so, we will have to check for null every time we want to use the file, as well as do the branching logic everywhere. This is tedious and error-prone. Such cases also rarely happen, and therefore the handling logic will also be rarely tested. When it happens, it often results in a fatal error, which is not a good user experience.

Solution: Use a NullFile Object

NullFile is a null object pattern implementation for files. It is a FileInterface that represents the missing file. It appears like a real file to the caller and can be considered a real file by most parts of the code.

Instead of a fatal error, your user will now be able to see if there is a file, but something is wrong with it, and will be able to tell the admin about it. The admin will be able to act on it, for example, by re-uploading the file or restoring it from backup.

The NullFile does that without much effort from the developer.

A NullFile will only stop acting like a real file if the caller is trying to operate on it that would cause a side effect. For example, you will not be able to copy a NullFile to another location, to change its content, etc. If that happens, NullFile will throw a NullFileOperationException.

Usage

Preparation

You need to install the package rekalogika/file-null to use this feature:

composer require rekalogika/file-null

An example on how to use NullFile in your entity:

use Rekalogika\Contracts\File\FileInterface;
use Rekalogika\Domain\File\Null\NullFile;

class Product {
private ?FileInterface $image = null;

public function __construct(FileInterface $image) {
$this->image = $image;
}

// The image must never be null. So, if the image does not exist, we
// substitute it with a NullFile instead.
public function getImage(): FileInterface {
return $this->image ?? new NullFile();
}
}

Checking for a Null File

All null files implement the NullFileInterface. You can use this interface to check if a file is null:

use Rekalogika\Contracts\File\NullFileInterface;

/** @var FileInterface $file */

if ($file instanceof NullFileInterface) {
// The file is null.
}

InvalidFile: a Null File That is Also an Exception

There is also a null file called InvalidFile which is identical to the standard NullFile, but also extends Exception. The idea is that it contains the stack trace where it was instantiated that can help you debug the problem, if you can log it somewhere. You can also throw it somewhere down the line if you need it.

To log an InvalidFile you can do something like the following.

use Rekalogika\Contracts\File\FileInterface;
use Psr\Log\LoggerInterface;

/** @var FileInterface $file */
/** @var LoggerInterface $logger */

if ($file instanceof InvalidFile) {
$logger->error('Invalid file', [
'exception' => $file,
]);
}