Пакет · sdk-s5w

Модель работы

Из одной идеи следует почти всё остальное: клиент SDK — это ключ к одному бакету. Он не хранит файлов и не знает про другие бакеты; он превращает вызовы методов в запросы к серверу, а ответы — в неизменяемые объекты.

Один клиент — один ключ — один бакет

В S5W бакет определяется ключом: в адресах API бакета нет вообще. Сервер смотрит на ключ и сам понимает, к какому бакету относится запрос. SDK повторяет эту модель:

text
S5w (клиент)  ──  S5wConfig  ──  адрес сервера + ключ  ──►  один бакет
                                                         │
                                       папки, файлы, загрузки этого бакета

Отсюда три следствия:

  • Бакет нигде не указывается. Ни в конфигурации, ни в методах. Ошибиться бакетом невозможно — в протоколе его просто нет.
  • Несколько бакетов — несколько клиентов. Каждому свой ключ и свой объект S5w. В Winter это несколько именованных бинов, см. Интеграция с Winter.
  • Права определяет ключ. Ключ BASIC годится только для check(); для папок, файлов и загрузок нужен FULL. SDK не проверяет это заранее — сервер ответит 403, и вы получите ForbiddenException.

Клиент и ресурсы

Клиент — точка входа. Сами операции сгруппированы по ресурсам API, как и на сервере:

Вызов Что внутри Адрес API
$s5w->check() проверка ключа, место в бакете GET /v1/check
$s5w->folders() папки /v1/folders
$s5w->files() файлы: список, чтение, загрузка, изменение, удаление /v1/files
$s5w->uploads() загрузка частями /v1/uploads

Ресурс создаётся при первом обращении и дальше переиспользуется — $s5w->files() можно вызывать сколько угодно раз, это ничего не стоит. Ни клиент, ни ресурсы не хранят состояния между вызовами, поэтому один экземпляр безопасно использовать во всём приложении.

Ответы — это снимки, а не живые объекты

Каждый метод возвращает объект, собранный из ответа сервера: File, Folder, Page, UploadSession. Все они неизменяемы (readonly) и описывают состояние на момент ответа.

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

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

$file->name;   // по-прежнему старое имя — $file это снимок до переименования

Чтобы получить новое состояние, возьмите то, что вернул метод изменения, или перечитайте объект:

php
$file = $s5w->files()->rename($file, 'new.txt');   // вернул новый снимок
$file = $s5w->files()->get($file);                  // или перечитать

Это же правило работает в загрузке частями: каждый append() возвращает новую UploadSession с новым смещением, а старую можно выбросить.

Объект вместо id

Методы, которые принимают id файла, принимают и сам объект File; параметр folder: принимает и имя папки, и объект Folder. Поэтому результат одного вызова можно сразу передавать в следующий.

Файл адресуется по id

У каждого файла есть id — 16 символов из A-Za-z0-9_- (на сервере это называется slug). Именно по нему файл находят методы SDK и адреса раздачи /p/{id} и /o/{bucket}/{id}.

  • id не меняется при переименовании и переносе — выданные ссылки продолжают работать;
  • id безопасно хранить у себя в базе, писать в логи и показывать в URL — внутренние номера сервер наружу не отдаёт;
  • имя — не идентификатор. Имя уникально только внутри папки, и при совпадении сервер сам добавит суффикс: report.pdf → report (1).pdf.

Строку, которая не может быть id, SDK отклоняет сам, без запроса: find('abc') вернёт null, а get('abc') бросит NotFoundException.

Что происходит при вызове

Возьмём $s5w->files()->upload('/tmp/video.mp4', folder: 'videos'):

text
files()->upload()
│
├─ файл ≤ 16 МиБ ──►  POST /v1/files (multipart, один запрос)
│
└─ файл > 16 МиБ ──►  uploads()->send()
                        ├─ sha256 файла ──► такое уже есть в бакете? → готово, ничего не передаём
                        ├─ POST   /v1/uploads              открыть сессию
                        ├─ PATCH  /v1/uploads/{id}  × N    части по 8 МиБ; сбой → спросить сервер, продолжить
                        └─ POST   /v1/uploads/{id}/complete собрать файл
│
▼
ответ сервера ──► проверка полей ──► File   (или исключение по статусу ответа)

Вы вызываете одно и то же и получаете один и тот же File — разница только внутри. Подробно — в разделе Конвейер загрузки.

Ошибки — по типу

Любой отказ сервера становится исключением определённого класса — по HTTP-статусу ответа. Все они наследуют S5wException:

php
try {
  $s5w->files()->move($file, 'archive');
} catch (NotFoundException) {
  // файла уже нет
} catch (ValidationException $e) {
  // нет такой папки; $e->errors() — ошибки по полям
} catch (ConflictException) {
  // в папке уже есть файл с таким именем
}

Отдельно от отказов сервера — TransportException (сервер вообще не ответил) и ResponseException (ответил, но не как S5W). Полная иерархия — в справочнике Исключения, практика — в руководстве Обработка ошибок.

Какой уровень API выбрать

Задача Чем пользоваться
загрузить файл, любого размера files()->upload() / uploadContent()
показать прогресс, сохранить id сессии, продолжить после перезапуска uploads()->send() / resume()
файл приходит кусками (например, пересылаете поток) или нужен свой цикл uploads()->start() / append() / complete()

В девяти случаях из десяти достаточно первой строки. Остальные уровни — для задач, где загрузку нужно контролировать: Большие файлы и Загрузка по шагам.