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.
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);
// 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);
$contents = Storage::get('avatars/photo.jpg');
$stream = Storage::readStream('videos/intro.mp4');
echo stream_get_contents($stream);
fclose($stream);
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'
Storage::copy('original.jpg', 'backup.jpg');
Storage::move('tmp/upload.jpg', 'final/photo.jpg');
Storage::delete('old-file.txt');
Storage::setVisibility('doc.pdf', 'private');
Storage::setVisibility('doc.pdf', 'public');
// 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);
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');
| 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. |
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;
}