Uploading files
Two methods upload: files()->upload() takes a file from disk, files()->uploadContent() takes
bytes from memory. Both return a ready File and pick how to transfer it by size.
A file from disk
$file = $s5w->files()->upload('/var/data/report.pdf');
$file->id; // "FXOkqegbdrNRWQgc"
$file->name; // "report.pdf"
$file->folder; // null — the file is in the bucket root
$file->content->size; // 248113Instead of a path you can pass an SplFileInfo — from a DirectoryIterator, for example:
foreach (new DirectoryIterator('/var/data/inbox') as $entry) {
if ($entry->isFile()) {
$s5w->files()->upload($entry, folder: 'inbox');
}
}If the file does not exist or cannot be read, the SDK throws S5wException before any request.
Into a folder
The folder: parameter takes a folder name or a Folder object:
$s5w->files()->upload($path, folder: 'invoices');
$avatars = $s5w->folders()->ensure('avatars', public: true);
$s5w->files()->upload($path, folder: $avatars);Without folder: the file lands in the bucket root. The folder must exist: if it does not,
you get ValidationException with Folder "…" does not exist. Creating it up front is easy with
folders()->ensure() — see Folders and files.
The folder decides visibility
A file has no “public” flag of its own — it comes from the folder. A file in a public folder is
served at publicUrl without a key, in a private one only with a key. A file in the bucket
root is public. If a file must not be open to everyone, put it into a private folder.
Under your own name
By default the file is stored under the source file’s name. The name: parameter sets another:
$s5w->files()->upload('/tmp/upload-7f3a.bin', folder: 'docs', name: 'Contract 42.pdf');The server keeps the extension honest: it is detected from the content, and if the name carries
a different known extension, the server replaces it. photo.png with a JPEG inside is stored as
photo.jpg.
A file from a user’s request
A file sent in a form is put by PHP into a temporary file with a random name. Pass only its path
and that random name is what ends up in the store. Pass the original name with name::
use Flytachi\Winter\Kernel\Http\Request\Annotation\RequestFile;
use Flytachi\Winter\Kernel\Http\Response\ResponseEntity;
use Flytachi\Winter\Kernel\Route\Annotation\PostMapping;
#[PostMapping('avatar')]
public function avatar(
#[RequestFile('file', maxSize: '10MB', accept: ['image/*'])] array $file,
): ResponseEntity {
$stored = $this->s5w->files()->upload(
$file['tmp_name'],
folder: 'avatars',
name: $file['name'],
);
return ResponseEntity::ok($stored);
}If you forget name
Without name: the file is stored as swoole.upfile.Ab3x.jpg — the server adds the right
extension, but the name stays temporary.
ResponseEntity::ok($stored) returns the file as JSON: every SDK entity implements
JsonSerializable, with dates as ISO 8601 strings.
From memory
When there is no file on disk — a PDF the application has just built, an image from base64, a
CSV export — use uploadContent(). The name is required here: the server takes the extension
from it for text formats.
// a generated document
$pdf = $renderer->render($invoice);
$s5w->files()->uploadContent($pdf, "invoice-{$invoice->id}.pdf", folder: 'invoices');
// an image from a data URL
[, $base64] = explode(',', $dataUrl, 2);
$s5w->files()->uploadContent(base64_decode($base64), 'signature.png', folder: 'signatures');Images: resize and re-encode
At upload the server can shrink an image, rotate it by EXIF and re-encode it. The options go in
an ImageOptions object:
use Flytachi\Winter\SDK\S5w\Option\ImageOptions;
$file = $s5w->files()->upload(
$file['tmp_name'],
folder: 'avatars',
name: $file['name'],
imageOptions: ImageOptions::webp(80)->fit(512, 512),
);Common choices:
| Task | Options |
|---|---|
| WebP, default quality | ImageOptions::webp() |
| WebP, fit into 1600×1600 | ImageOptions::webp()->fit(1600, 1600) |
| only shrink by width, keep the format | ImageOptions::original()->fit(800) |
| AVIF at quality 50 | ImageOptions::avif(50) |
| JPEG, fit into 1920 wide | ImageOptions::jpeg(85)->fit(1920) |
fit() only shrinks and keeps the aspect ratio: a 320×200 image in a 160×160 box becomes
160×100. The original is not kept — the result is what lands in the bucket. The name follows
the format: photo.png re-encoded to WebP is stored as photo.webp. Every parameter and limit
is in the ImageOptions reference.
What the server did is in the answer’s processed field:
$p = $file->processed;
$p->applied; // true
$p->operations; // ["resize:400x267", "encode:webp@80"]
$p->source->width; // 1200
$p->result->width; // 400
$p->saved(); // 4251 — bytes savedIf the server decided to do nothing, applied is false and reason explains why:
nothing to do (already smaller than the box and in the right format), animated (animations
are left alone), output is larger than source (the result came out heavier, the original was
kept).
Options for images only
Processing options sent with a file that is not an image are refused by the server:
ValidationException with Image options were given, but this file is not an image. If one code
path uploads files of different types, pass imageOptions only for images.
The processed field exists only in the answer to an upload. When you read the file later with
get() or list(), it is null — the server does not keep the processing report.
Duplicate names and content
Same name. A name is unique within a folder. If a file with that name is already there, the server does not overwrite it but stores the new one under a numbered name:
$s5w->files()->upload('/tmp/notes.txt', folder: 'docs')->name; // "notes.txt"
$s5w->files()->upload('/tmp/notes.txt', folder: 'docs')->name; // "notes (1).txt"Same content. The server stores identical bytes once per bucket (by SHA-256). A second file
with the same content is a separate file with its own id and name, but it takes no space in the
bucket. The deduplicated flag tells you so:
$copy = $s5w->files()->upload('/tmp/notes.txt', folder: 'docs');
$copy->deduplicated; // true — the bytes were already in the bucket, no disk spentDeleting one such file does not affect the others.
Large files
Nothing to do: upload() and uploadContent() switch to chunks themselves when the file is
larger than the threshold (16 MiB by default).
| Size | How it is sent |
|---|---|
| up to and including 16 MiB | one POST /v1/files request |
| larger than 16 MiB | in chunks through /v1/uploads: broken chunks retried, content already in the bucket not sent |
The threshold is the chunkThreshold parameter of S5wConfig (at most ~31.9 MiB — the server’s
single-request limit). For progress and resuming after a failure call uploads()->send()
directly — see Large files.
When there is no space
When the bucket’s quota is full, an upload gets PayloadTooLargeException (HTTP 413). You can
check free space up front with check():
$storage = $s5w->check()->storage;
if (!$storage->fits(filesize($path))) {
throw new RuntimeException('Not enough space in the bucket');
}The check guarantees nothing: someone else may take the space between check() and the upload,
so PayloadTooLargeException is still worth handling.
See also
Files::upload()— every parameterImageOptions— formats and limits- Image processing on the server — what S5W actually does