Framework
Utilities

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:

ModeDoes
Image::ResizeScale to the given size.
Image::MaximumScale down only when larger than the box.
Image::ThumbScale 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.