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:
S5w (client) ── S5wConfig ── server address + key ──► one bucket
│
its folders, files and uploadsThree 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
S5wobject. 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 getForbiddenException.
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.
$file = $s5w->files()->get($id);
$s5w->files()->rename($file, 'new.txt');
$file->name; // still the old name — $file is the snapshot from before the renameTo see the new state, take what the changing method returned, or read the object again:
$file = $s5w->files()->rename($file, 'new.txt'); // returned a new snapshot
$file = $s5w->files()->get($file); // or read it againThe 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'):
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:
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.