FILE STORAGE & UPLOAD

Last updated: June 29th 2026

STORAGE SYSTEM

The framework provides a Flysystem-powered storage system with a Storage facade for low-level filesystem operations and FileUpload for handling HTTP file uploads. Supports local disk and S3 via a unified API.

Get Started

Configuration

Create app/Config/Storage.php to define your disks:

define('STORAGE_DISKS', serialize([
    'public' => [
        'driver' => 'local',
        'root' => getcwd() . '/public/storage',
        'url' => '/storage',
    ],
    'private' => [
        'driver' => 'local',
        'root' => getcwd() . '/storage/app/private',
        'url' => null,
    ],
    's3' => [
        'driver' => 's3',
        'key' => $_ENV['AWS_ACCESS_KEY_ID'] ?? '',
        'secret' => $_ENV['AWS_SECRET_ACCESS_KEY'] ?? '',
        'region' => $_ENV['AWS_DEFAULT_REGION'] ?? 'us-east-1',
        'bucket' => $_ENV['AWS_BUCKET'] ?? '',
        'url' => $_ENV['AWS_URL'] ?? null,
    ],
]));

define('STORAGE_DEFAULT', 'public');

Then initialise in your front controller (public/index.php):

use Simple\Storage\Storage;

Storage::configure(unserialize(STORAGE_DISKS), STORAGE_DEFAULT);

Storage API

Writing Files

// String contents
Storage::write('avatars/photo.jpg', $fileContents);
Storage::write('report.pdf', $pdfData, 'private');

// From a stream (efficient for large files)
Storage::writeStream('videos/intro.mp4', fopen($tmpPath, 'rb'));

// Write to a specific disk
Storage::disk('s3')->write('backup.sql', $dump);

Reading Files

$contents = Storage::get('avatars/photo.jpg');

$stream = Storage::readStream('videos/intro.mp4');
echo stream_get_contents($stream);
fclose($stream);

File Metadata

Storage::exists('avatars/photo.jpg');       // bool
Storage::size('avatars/photo.jpg');          // bytes
Storage::mimeType('report.pdf');             // 'application/pdf'
Storage::lastModified('avatars/photo.jpg');  // unix timestamp
Storage::visibility('report.pdf');           // 'public' or 'private'

Copy, Move, Delete

Storage::copy('original.jpg', 'backup.jpg');
Storage::move('tmp/upload.jpg', 'final/photo.jpg');
Storage::delete('old-file.txt');

Visibility

Storage::setVisibility('doc.pdf', 'private');
Storage::setVisibility('doc.pdf', 'public');

URLs

// Public URL for a file on the local disk
$url = Storage::url('avatars/photo.jpg');
// /storage/avatars/photo.jpg

// Signed URL for S3 private files (expires in 1 hour)
$url = Storage::disk('s3')->temporaryUrl('report.pdf', 3600);

File Upload

The FileUpload class handles $_FILES and delegates to the Storage system:

use Simple\FileUpload;

$upload = new FileUpload('avatar');

// Auto-named (sha1 hash + extension)
$path = $upload->store('avatars');
// Returns: 'avatars/abc123def.jpg'

// Custom filename
$path = $upload->storeAs('avatars', 'my-avatar.jpg');

// Specify a disk
$path = $upload->storeAs('reports', 'report.pdf', 's3');

// Keep original filename (sanitized)
$path = $upload->storeAs('uploads', $upload->getOriginalName());

// Validate before storing
$upload->validateTypes(['jpg', 'png']);
$upload->validateMaxSize(2048); // KB
$path = $upload->store('avatars');

Available Methods

Method Returns Description
getOriginalName() string Original uploaded filename
getSize() int File size in bytes
getClientExtension() string File extension from the upload
getClientMimeType() string MIME type from $_FILES
getHash() string SHA1 hash of the file contents
validateTypes(array) $this Restrict allowed extensions (chainable)
validateMaxSize(int) $this Set max size in KB (chainable)
store(string $dir = '', string|null $disk = null) string Store with auto-generated hash name. Returns the path.
storeAs(string $path, string $name, string|null $disk = null) string Store with a custom filename. Returns the path.

Serving Private Files

Files stored on a private disk are not served by the web server directly. Create a controller to serve them behind authentication:

// Routes.php
Router::get('download/{path}', 'File@download');


// FileController.php
public function download($path)
{
    $disk = Storage::disk('private');
    if (!$disk->exists($path)) {
        throw new \Exception('File not found', 404);
    }

    header('Content-Type: ' . $disk->mimeType($path));
    header('Content-Disposition: attachment; filename="' . basename($path) . '"');
    echo $disk->get($path);
    exit;
}