Package · sdk-s5w

Mental model

Almost everything follows from one idea: an SDK client is a key to one bucket. It holds no files and knows nothing about other buckets; it turns method calls into requests to the server, and answers into immutable objects.

One client — one key — one bucket

In S5W the bucket is decided by the key: the API addresses contain no bucket at all. The server looks at the key and knows which bucket the request belongs to. The SDK follows the same model:

text
S5w (client)  ──  S5wConfig  ──  server address + key  ──►  one bucket
                                                         │
                                       its folders, files and uploads

Three consequences:

  • The bucket is never specified — neither in the configuration nor in methods. You cannot pick the wrong bucket: it is simply not part of the protocol.
  • Several buckets — several clients. Each with its own key and its own S5w object. In Winter these are several named beans, see Winter integration.
  • The key decides the rights. A BASIC key is good only for check(); folders, files and uploads need FULL. The SDK does not check this up front — the server answers 403 and you get ForbiddenException.

The client and its resources

The client is the entry point. The operations themselves are grouped by API resource, as on the server:

Call What’s inside API address
$s5w->check() key check, space in the bucket GET /v1/check
$s5w->folders() folders /v1/folders
$s5w->files() files: list, read, upload, change, delete /v1/files
$s5w->uploads() chunked uploads /v1/uploads

A resource is created on first use and reused afterwards — calling $s5w->files() any number of times costs nothing. Neither the client nor the resources keep state between calls, so one instance is safe to use across the whole application.

Answers are snapshots, not live objects

Every method returns an object built from the server’s answer: File, Folder, Page, UploadSession. They are all immutable (readonly) and describe the state at the moment of the answer.

php
$file = $s5w->files()->get($id);

$s5w->files()->rename($file, 'new.txt');

$file->name;   // still the old name — $file is the snapshot from before the rename

To see the new state, take what the changing method returned, or read the object again:

php
$file = $s5w->files()->rename($file, 'new.txt');   // returned a new snapshot
$file = $s5w->files()->get($file);                  // or read it again

The same rule holds for chunked uploads: every append() returns a new UploadSession with the new offset; the old one can be thrown away.

An object instead of an id

Methods that take a file id also take the File itself; the folder: parameter takes a folder name or a Folder. So the result of one call can go straight into the next.

A file is addressed by its id

Every file has an id — 16 characters of A-Za-z0-9_- (the server calls it a slug). SDK methods and the delivery addresses /p/{id} and /o/{bucket}/{id} find the file by it.

  • the id does not change on rename or move — links handed out keep working;
  • the id is safe to store in your database, write to logs and show in URLs — the server never exposes internal numbers;
  • a name is not an identifier. A name is unique only within a folder, and on a clash the server adds a suffix itself: report.pdf → report (1).pdf.

A string that cannot be an id is rejected by the SDK itself, with no request: find('abc') returns null, and get('abc') throws NotFoundException.

What happens on a call

Take $s5w->files()->upload('/tmp/video.mp4', folder: 'videos'):

text
files()->upload()
│
├─ file ≤ 16 MiB ──►  POST /v1/files (multipart, one request)
│
└─ file > 16 MiB ──►  uploads()->send()
                        ├─ sha256 of the file ──► already in the bucket? → done, nothing sent
                        ├─ POST   /v1/uploads              open a session
                        ├─ PATCH  /v1/uploads/{id}  × N    8 MiB chunks; failure → ask the server, continue
                        └─ POST   /v1/uploads/{id}/complete assemble the file
│
▼
server answer ──► field checks ──► File   (or an exception chosen by status)

You call the same thing and get the same File — the difference is only inside. In detail — in Upload pipeline.

Errors — by type

Any server refusal becomes an exception of a specific class, chosen by the HTTP status. They all extend S5wException:

php
try {
  $s5w->files()->move($file, 'archive');
} catch (NotFoundException) {
  // the file is gone
} catch (ValidationException $e) {
  // no such folder; $e->errors() — per-field errors
} catch (ConflictException) {
  // the folder already has a file with that name
}

Separate from server refusals are TransportException (the server did not answer at all) and ResponseException (it answered, but not like S5W). The full hierarchy is in the Exceptions reference, the practice — in Error handling.

Which API level to pick

Task Use
upload a file, any size files()->upload() / uploadContent()
show progress, keep the session id, resume after a restart uploads()->send() / resume()
the file arrives in pieces (e.g. you relay a stream), or the loop must be yours uploads()->start() / append() / complete()

Nine times out of ten the first row is enough. The other levels are for tasks where the upload has to be under your control: Large files and Step-by-step uploads.