Package · sdk-s5w

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

php
$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;   // 248113

Instead of a path you can pass an SplFileInfo — from a DirectoryIterator, for example:

php
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:

php
$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:

php
$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::

main/Controllers/AvatarController.php
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.

php
// 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:

php
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:

php
$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 saved

If 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:

php
$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:

php
$copy = $s5w->files()->upload('/tmp/notes.txt', folder: 'docs');
$copy->deduplicated;   // true — the bytes were already in the bucket, no disk spent

Deleting 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():

php
$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