Загрузка обязательного файла до создания модели
Многие сущности имеют обязательные поля типа Файл. При этом по дизайн-гайду, передавать в одном запросе поля сущности и файл неправильно. Это неудобно - в случае ошибки валидации пользователю придётся повторить запрос и снова загрузить файл. Такой запрос нужно разбивать на два: один для загрузки файла, второй - для создания сущности. Часто мы не можем загрузить файл после создания сущности - не позволяет дизайн или бизнес-логика. Поэтому файл нужно загрузить в систему до создания сущности, а после, создать сущность, сообщив ей, что она должна использовать ранее загруженный файл.
В Ensi для решения этой проблемы используется т.н. временные файлы. Временный файл - это запись в особой таблице, которая позволяет сослаться на файл для его прикрепления к сущности, или же удалить файл через какое-то время, если он не был ни к чему прикреплён.
Разберём реализацию на примере сервиса PIM.
UploadFileAction
Метод execute принимает следующие агрументы:
UploadedFile $file
string $folder
папка относительно корня публичного диска текущего бизнес-доменаstring $fileNamePrefix
префикс, добавлямый к случайному имени файла
Загруженный файл будет размещён как $folder/hash{0-2}/hash{2-4}/$fileNamePrefix_rand{20}.ext
,
где:
hash{0-2}
- это первые два символа хэша от оригинального имения файлаhash{2-4}
- вторые два символа хэшаrand{20}
- случайная строка длинной 20 символовext
- оригинальное расширение файла
В ответ вы получите экземпляр класса TempFile
.
PreloadFileResource
Классу TempFile соотвествует ресурс PreloadFileResource, который имеет следующую структуру:
return [
'preload_file_id' => $this->id,
'file' => [
'path' => $file->path,
'root_path' => $file->rootPath,
'url' => $file->url,
],
];
Т.е. это id временного файла и рядом обычный ресурс файла, который содержит разные варианты пути до файла.
TempFile
Класс TempFile - это обычная Eloquent модель, у которой есть два вспомогательных метода:
$tempFile->evict()
- удаляет модель и возвращает путь до файлаTempFile::grab($tempFileId)
- находит файл по id и вызываетevict()
возвращая его результат
Получив запрос на создание сущности, в которой передан preload_file_id
вы можете сделать вот так:
$myModel = new MyModel();
// ...
$myModel->file_path = TempFile::grab($preloadFileId);
$myModel->save();
Очистка системы от ненужных временных файлов
В сервисе должна быть реализована консольная команда для периодической очистки хранилища от временных файлов.
Принцип работы команды прост - находим все записи в таблице temp_files
у которых created_at
старше определённого срока
хранения файлов, и удаляем файл и запись в таблице.
$timestamp = Date::now()->subHours(self::STORAGE_TIME);
$records = TempFile::where('created_at', '<', $timestamp)->lazyById();
foreach ($records as $record) {
try {
$this->deleteFile($disk, $record);
} catch (Exception $e) {
$this->error("Ошибка при удалении файла {$record->path}: {$e->getMessage()}");
}
}