Загрузка файлов
Для загрузки есть два метода: files()->upload() берёт файл с диска, files()->uploadContent()
— байты из памяти. Оба возвращают готовый File и сами выбирают способ передачи по размеру.
Файл с диска
$file = $s5w->files()->upload('/var/data/report.pdf');
$file->id; // "FXOkqegbdrNRWQgc"
$file->name; // "report.pdf"
$file->folder; // null — файл в корне бакета
$file->content->size; // 248113Вместо пути можно передать SplFileInfo — например, из DirectoryIterator:
foreach (new DirectoryIterator('/var/data/inbox') as $entry) {
if ($entry->isFile()) {
$s5w->files()->upload($entry, folder: 'inbox');
}
}Если файла нет или его нельзя прочитать, SDK бросит S5wException ещё до запроса.
В папку
Параметр folder: принимает имя папки или объект Folder:
$s5w->files()->upload($path, folder: 'invoices');
$avatars = $s5w->folders()->ensure('avatars', public: true);
$s5w->files()->upload($path, folder: $avatars);Без folder: файл ложится в корень бакета. Папка должна существовать: если её нет, будет
ValidationException с сообщением Folder "…" does not exist. Создать её заранее удобно
через folders()->ensure() — см. Работа с папками и файлами.
Публичность определяет папка
У файла нет собственного флага «публичный» — он берётся из папки. Файл в публичной папке
доступен по publicUrl без ключа, в приватной — только с ключом. Файл в корне бакета
публичный. Если файл не должен быть доступен всем, кладите его в приватную папку.
Под своим именем
По умолчанию файл сохраняется под именем исходного файла. Параметр name: задаёт другое:
$s5w->files()->upload('/tmp/upload-7f3a.bin', folder: 'docs', name: 'Договор №42.pdf');Сервер следит за расширением: оно определяется по содержимому, и если в имени указано
другое известное расширение, сервер заменит его. Файл photo.png, внутри которого JPEG,
сохранится как photo.jpg.
Файл из запроса пользователя
Файл, присланный в форме, PHP кладёт во временный файл со случайным именем. Если передать
только путь к нему, в хранилище попадёт это случайное имя. Передавайте исходное имя через
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);
}Если забыть name
Без name: файл сохранится как swoole.upfile.Ab3x.jpg — сервер добавит правильное
расширение, но имя останется временным.
ResponseEntity::ok($stored) отдаст файл в JSON: все сущности SDK реализуют
JsonSerializable, даты в них — строки ISO 8601.
Из памяти
Когда файла на диске нет — PDF, который только что собрало приложение, картинка из base64,
экспорт в CSV, — используйте uploadContent(). Имя здесь обязательно: по нему сервер
выберет расширение для текстовых форматов.
// сгенерированный документ
$pdf = $renderer->render($invoice);
$s5w->files()->uploadContent($pdf, "invoice-{$invoice->id}.pdf", folder: 'invoices');
// картинка из data-URL
[, $base64] = explode(',', $dataUrl, 2);
$s5w->files()->uploadContent(base64_decode($base64), 'signature.png', folder: 'signatures');Картинки: уменьшить и перекодировать
Сервер умеет при загрузке уменьшить картинку, повернуть её по EXIF и перекодировать. Опции
передаются объектом ImageOptions:
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),
);Частые варианты:
| Задача | Опции |
|---|---|
| WebP, качество по умолчанию | ImageOptions::webp() |
| WebP, вписать в 1600×1600 | ImageOptions::webp()->fit(1600, 1600) |
| только уменьшить по ширине, формат оставить | ImageOptions::original()->fit(800) |
| AVIF с качеством 50 | ImageOptions::avif(50) |
| JPEG, вписать в 1920 по ширине | ImageOptions::jpeg(85)->fit(1920) |
fit() только уменьшает и сохраняет пропорции: картинка 320×200 в рамке 160×160 станет
160×100. Оригинал не хранится — в бакет попадает результат. Имя следует за форматом:
photo.png, перекодированный в WebP, сохранится как photo.webp. Все параметры и пределы —
в справочнике ImageOptions.
Что сделал сервер, видно в поле processed ответа:
$p = $file->processed;
$p->applied; // true
$p->operations; // ["resize:400x267", "encode:webp@80"]
$p->source->width; // 1200
$p->result->width; // 400
$p->saved(); // 4251 — байт сэкономленоЕсли сервер решил ничего не делать, applied будет false, а reason объяснит почему:
nothing to do (картинка уже меньше рамки и в нужном формате), animated (анимации не
трогаются), output is larger than source (результат вышел тяжелее, сохранён оригинал).
Опции только для картинок
Опции обработки, переданные с файлом, который не является картинкой, сервер отклоняет:
ValidationException с сообщением Image options were given, but this file is not an image.
Если вы загружаете файлы разных типов одним кодом, передавайте imageOptions только для
картинок.
Поле processed есть только в ответе на загрузку. Когда вы позже читаете файл через
get() или list(), оно равно null — сервер не хранит отчёт об обработке.
Дубликаты имён и содержимого
Одинаковые имена. Имя уникально внутри папки. Если файл с таким именем уже есть, сервер не перезапишет его, а сохранит новый под именем с номером:
$s5w->files()->upload('/tmp/notes.txt', folder: 'docs')->name; // "notes.txt"
$s5w->files()->upload('/tmp/notes.txt', folder: 'docs')->name; // "notes (1).txt"Одинаковое содержимое. Сервер хранит одинаковые байты один раз на бакет (по SHA-256).
Второй файл с тем же содержимым — это отдельный файл со своим id и именем, но место в бакете
он не занимает. Об этом говорит флаг deduplicated:
$copy = $s5w->files()->upload('/tmp/notes.txt', folder: 'docs');
$copy->deduplicated; // true — байты уже были в бакете, диск не потраченУдаление одного из таких файлов не затрагивает другие.
Большие файлы
Ничего делать не нужно: upload() и uploadContent() сами переходят на загрузку частями,
когда файл больше порога (по умолчанию 16 МиБ).
| Размер | Как уходит |
|---|---|
| до 16 МиБ включительно | одним запросом POST /v1/files |
| больше 16 МиБ | частями через /v1/uploads: с повтором оборванных частей и проверкой, нет ли такого содержимого в бакете |
Порог меняется параметром chunkThreshold в S5wConfig (не больше ~31,9 МиБ — это предел
одного запроса на сервере). Для прогресса и продолжения после сбоя вызывайте
uploads()->send() напрямую — см. Большие файлы.
Если места нет
Когда квота бакета заполнена, загрузка получает PayloadTooLargeException (HTTP 413).
Проверить свободное место заранее можно через check():
$storage = $s5w->check()->storage;
if (!$storage->fits(filesize($path))) {
throw new RuntimeException('В бакете не хватает места');
}Проверка заранее не гарантирует результат: между check() и загрузкой место может занять
кто-то ещё, поэтому PayloadTooLargeException всё равно стоит обрабатывать.
См. также
Files::upload()— все параметрыImageOptions— форматы и пределы- Обработка картинок на сервере — что именно делает S5W