Files & Storage
Read and write files, handle uploads, classify and process images, and resolve every storage path through generated accessors.
Storage
Storage is the filesystem layer — every read, write and path operation goes through it, so paths
are built consistently and nothing touches PHP's filesystem functions directly.
Reading & writing
use Framework\File\Storage;
$text = Storage::readFile($path);
Storage::writeFile($path, "contents");
Storage::createFile($dir, "notes.txt", "contents");
Storage::fileExists($path);
Storage::copyFile($from, $to);
Storage::moveFile($from, $to);
Storage::deleteFile($path);
Storage::getModifiedTime($path); // the last modified timestamp
Storage::readUrl($url); // read a remote file
Storage::writeFromUrl($path, $url); // download it to disk
Directories
Creating, emptying and listing folders. The listing helpers take a recursive flag, and
skipVendor to leave dependencies out:
Storage::createDir($dir);
Storage::ensureDir($dir); // create it only when missing
Storage::emptyDir($dir); // remove its contents
Storage::deleteDir($dir); // remove it entirely
Storage::getFilesInDir($dir, recursive: true);
Storage::getDirectoriesInDir($dir);
Storage::getAllInDir($dir); // files and folders
Storage::getFirstFileInDir($dir);
Paths & names
Joining and picking apart a path, without worrying about slashes:
Storage::parsePath("files", 7, "photo.png"); // "files/7/photo.png"
Storage::parseName("My Photo.PNG"); // a safe file name
Storage::getFileName("dir/photo.png"); // "photo"
Storage::getBaseName("dir/photo.png"); // "photo.png"
Storage::getDirectory("dir/photo.png"); // "dir"
Storage::getExtension("photo.PNG"); // "png"
Storage::hasExtension($path, "png", "jpg");
Storage::addLastSlash($dir); // and removeLastSlash()
Storage::addFirstSlash($path); // and removeFirstSlash()
Zip files
Bundling files for a download, or unpacking an upload:
Storage::createZip($zipPath, $files);
Storage::extractZip($zipPath, $toDir);
File uploads
File wraps a single uploaded file. Build one from the
request or from raw content, check it, then move it into place:
use Framework\File\File;
$file = File::fromRequest("avatar");
$file = File::fromContent($bytes, "note.txt", "text/plain");
$file->hasFile(); // was anything uploaded?
$file->isValid(); // and no upload error
$file->hasSizeError(); // it exceeded the size limit
$file->isImage(); // and isValidImage()
$file->getName(); // the original name
$file->getExtension();
$file->getType(); // the mime type
$file->hasExtension("png", "jpg");
$file->upload($destDir, "target.png");
$file->delete();
parseName() makes the uploaded name safe to store, and getCurlFile() turns the file
into a payload for forwarding it to another API.
File types
FileType classifies a file from its extension, so the UI can pick an icon and the code can accept
only what it should:
use Framework\File\FileType;
FileType::isImage($path); // and isPDF, isPNG, isICO
FileType::isVideo($path); // and isAudio
FileType::isDocument($path); // and isSpreadsheet, isPresentation
FileType::isText($path); // and isCode
FileType::isZip($path);
FileType::isDir($path); // and isFile, isHidden
FileType::getExtension($path);
FileType::getMimeType($path); // "image/jpeg"
FileType::getIcon($path); // the icon name for the type
Images
Image resizes and crops with GD, reading the EXIF orientation so photos are not left sideways.
The three modes decide how the target box is filled:
| Mode | Does |
|---|---|
Image::Resize | Scale to the given size. |
Image::Maximum | Scale down only when larger than the box. |
Image::Thumb | Scale and crop to fill the box exactly. |
use Framework\File\Image;
Image::resize($src, $dst, 800, 600, Image::Resize);
Image::resizeCrop($src, $dst, 300, 300);
Image::thumbnail($src, $dst, 150, 150);
Image::resample($src, $dst); // rewrite honouring the EXIF orientation
Image::getSize($path); // [ width, height ]
Image::getSizeFromUrl($url);
Image::getOrientation($path);
Image::getMimeType($path); // and getContentType, getExtension
Image::hasTransparency($path);
Image::isValidType($path);
Picture
Where Image transforms a file on disk, Picture is for drawing —
you open an image, paint on it, and send it straight to the browser. It is an instance rather than a set of static
helpers, and exposes the size it loaded:
use Framework\File\Picture;
$picture = new Picture($path);
$picture->width; // the loaded size
$picture->height;
$picture->type;
Colors are allocated on the image before they can be used, then writeText() prints with a
TrueType font — optionally centred on the given point:
$color = $picture->createColor(255, 255, 255);
$picture->writeText(
text: "Sold out",
x: 20,
y: 40,
color: $color,
fontFile: $fontPath,
fontSize: 18,
centered: true,
);
Finally print() sends the right Content-Type and outputs the image, so a
route can return a generated picture. Pass
download: true to have the browser save it instead of showing it:
$picture->print(); // render it inline
$picture->print(download: true, name: "ticket"); // save as ticket.png
Generated avatars, watermarks and share images are all built this way.
The media library
MediaFile manages the files a user uploads through a media browser — the tree of folders, the
files inside, and the thumbnails that go with them:
use Framework\File\MediaFile;
MediaFile::getList($path); // the files and folders to render
MediaFile::exists($path, $name);
MediaFile::uploadFile($path, $file);
MediaFile::createDir($path, $name);
MediaFile::renamePath($path, $oldName, $newName);
MediaFile::movePath($fromPath, $toPath, $name);
MediaFile::deletePath($path, $name);
MediaFile::getPath($path); // and getUrl()
MediaFile::getThumbPath($path); // and getThumbUrl()
Moving, renaming or deleting a file carries its thumbnail along, so the two never drift apart.
FileList
FileList is what a media listing is built with. You add an entry per file or folder — with its
source and thumbnail paths and urls — and read the result back sorted, folders first:
use Framework\File\FileList;
$list = new FileList();
$list->addBack($parentPath); // the ".." entry, when inside a folder
$list->add(
name: $name,
path: $path,
isDir: false,
sourcePath: $sourcePath,
sourceUrl: $sourceUrl,
thumbPath: $thumbPath,
thumbUrl: $thumbUrl,
);
$list->getSorted(); // or get() for the insertion order
Each entry becomes a FileItem, which is the shape the front end renders. Besides the name
and urls it carries the flags a file browser needs — isDir, isImage,
isPDF, isAudio, isDocument, isTransparent and
isBack — plus the icon for its type and the image
width and height. The companion MediaType enum names the kind of
media being browsed.
Registered paths
FilePath owns the storage layout. The framework ships the source,
thumbs, avatars and temp paths, and an app
registers whatever else it needs:
// config/File.config.php
use Framework\File\FilePath;
FilePath::register("exports");
FilePath::registerDirectory("archive");
At runtime it resolves those paths, plus the special ones — a private path the web server does not serve, a temp path per user, and the system temp directory:
FilePath::getPath("exports", $fileName); // and getDir(), getUrl()
FilePath::getPrivatePath($fileName);
FilePath::getTempPath(); // and getTempUrl()
FilePath::getSystemTempPath();
FilePath::getFTPPath();
The folders are created for you: ./framework
ensurePaths makes the base ones, and createDirs() makes the per-record sub-directories.
The Path class
Rather than passing those path names around as strings, the build turns every
registered path into a typed Path class — a directory, filesystem path and url accessor per path:
use Framework\System\Path;
Path::getSourceDir("7", "photo.png"); // relative dir
Path::getSourcePath("7", "photo.png"); // absolute path on disk
Path::getSourceUrl("7", "photo.png"); // public url
Register an exports path and you get Path::getExportsDir(),
getExportsPath() and getExportsUrl() after the next build — so a typo becomes a
compile-time error instead of a missing file. A migration can
create the per-record sub-directories as data is added.