Skip to content

This chapter needs an extra package

composer require naf/storage

File storage

Files that belong to your application: an uploaded avatar, a generated invoice, an export somebody downloads once. naf/storage gives them one API whether they sit on the local disk, in an S3-compatible bucket or on a WebDAV server.

Disks

A disk is a configured place with a name, and naming them is the point:

use function Naf\Storage\storage;

storage()->put('foo.txt', 'Hello World');
storage('documents')->put('invoices/2026/1234.pdf', $contents);
storage('public')->url('avatars/123.jpg');

storage() without a name is the default disk. storage('documents') is somewhere else entirely — its own root, its own visibility, possibly its own adapter. Public assets and private documents stay apart because they are different disks, not because everybody remembers which directory is which.

The everyday operations are what you would expect:

$disk = storage('documents');

$disk->put($path, $contents);
$disk->get($path);
$disk->exists($path);
$disk->delete($path);
$disk->url($path);        // public disks

Large files

Reading a 2 GB export into a string to write it somewhere else is how an application runs out of memory. Streams avoid that in both directions:

$stream = $disk->readStream($path);
$disk->writeStream($path, $handle);

Both ends of the transfer stay bounded, which is what makes a file bigger than the memory limit possible at all.

Paths are checked, not trusted

A path that climbs out of its disk's root is refused, not normalised into something harmless-looking. Failures are typed exceptions rather than false, so a mistake is visible where it happens instead of three lines later.

This is not an upload lifecycle

Quotas, MIME policy, who may read what, and whether a file is allowed to exist at all remain your application's decisions. This package moves bytes and says plainly when it cannot.

S3 and WebDAV

Both are opt-in, and neither is installed unless you ask for it:

composer require naf/client        # the streaming HTTP transport both need
composer require aws/aws-sdk-php   # S3 only

They are declared as suggestions rather than requirements, so a local-only installation pulls in nothing extra. The adapter boundary is a single interface, so a service this package does not ship is a class and a binding away.

Remote transfers stream too, which means the temporary filesystem needs room for what is in flight — that is disk, not memory.