CFile::SaveFile — низкоуровневый способ сохранить файл
в системе управления файлами Битрикса вручную, минуя стандартную
форму загрузки. Разберём, когда он нужен и какие подводные камни
есть у прямого сохранения файла из кода.
Базовый вызов
<?php
$fileArray = [
'name' => 'report.pdf',
'type' => 'application/pdf',
'tmp_name' => '/tmp/generated_report_123.pdf',
'size' => filesize('/tmp/generated_report_123.pdf'),
];
$fileId = CFile::SaveFile($fileArray, 'reports');
if ($fileId) {
echo 'Файл сохранён, ID: ' . $fileId;
}
Метод ожидает массив в формате, похожем на структуру
$_FILES, — с полями name, type,
tmp_name и size. Второй параметр —
подпапка внутри каталога upload/, куда физически попадёт
файл.
Сохранение сгенерированного контента без временного файла на диске
Частый сценарий — файл существует только в памяти (например, сформированный CSV-экспорт), и создавать его сначала на диске избыточно:
<?php
$csvContent = "Название;Цена\nТовар 1;1000\nТовар 2;2000\n";
$tmpPath = sys_get_temp_dir() . '/' . uniqid('export_') . '.csv';
file_put_contents($tmpPath, $csvContent);
$fileId = CFile::SaveFile([
'name' => 'export.csv',
'type' => 'text/csv',
'tmp_name' => $tmpPath,
'size' => filesize($tmpPath),
], 'exports');
unlink($tmpPath); // временный файл больше не нужен, CFile уже скопировал его
tmp_name обязателен — метод физически копирует файл
оттуда в целевой каталог, поэтому файл должен реально существовать
на диске в момент вызова, пусть даже временно.
Нужна помощь с Битрикс?
Привязка файла к элементу инфоблока
<?php
$fileId = CFile::SaveFile($fileArray, 'catalog');
$el = new CIBlockElement;
$el->Update($elementId, [
'PROPERTY_VALUES' => [
'DOCUMENT' => $fileId,
],
]);
Полученный $fileId — это ID записи в таблице файлов
(b_file), который можно записать в файловое свойство
инфоблока, поле пользователя или любое другое место, ожидающее ID
файла Битрикса.
Удаление файла
<?php
CFile::Delete($fileId);
Важная деталь: Delete удаляет запись и физический файл
только если на него не осталось других ссылок в системе. Если тот же
$fileId используется где-то ещё (например, в нескольких
элементах через общее свойство), метод уменьшает счётчик ссылок,
а сам файл остаётся на диске до полного освобождения.
Замена файла без потери старого ID
<?php
$newFileId = CFile::SaveFile($newFileArray, 'catalog');
CFile::Delete($oldFileId); // старый файл корректно освобождается
$el->Update($elementId, [
'PROPERTY_VALUES' => ['DOCUMENT' => $newFileId],
]);
Частая ошибка — забыть удалить старый файл при замене, из-за чего
каталог upload/ постепенно накапливает файлы-сироты,
на которые уже никто не ссылается из базы данных.
Проверка типа и размера перед сохранением
SaveFile не проверяет содержимое файла на соответствие
заявленному MIME-типу и не ограничивает размер — эти проверки
на стороне вызывающего кода обязательны, если файл формируется
из данных, на которые нельзя полагаться (например, приходит через
внешний API или загружается пользователем окольным путём, минуя
штатную форму с её собственными проверками):
<?php
$allowedExtensions = ['pdf', 'csv', 'xlsx'];
$ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
if (!in_array($ext, $allowedExtensions, true)) {
throw new \InvalidArgumentException('Недопустимый тип файла');
}
if (filesize($tmpPath) > 10 * 1024 * 1024) {
throw new \InvalidArgumentException('Файл превышает 10 МБ');
}
Частые ошибки
- tmp_name указывает на несуществующий файл. Метод не сможет скопировать файл и вернёт ошибку сохранения.
- Забыли удалить старый файл при замене. Каталог upload/ постепенно накапливает файлы-сироты.
- Нет проверки типа и размера при программном сохранении. В систему попадают файлы, которые не прошли бы штатную форму загрузки.
Итог
CFile::SaveFile сохраняет файл из временного пути
в файловую систему Битрикса и возвращает ID, пригодный для любого
файлового поля системы. При замене файла важно освобождать старый
через CFile::Delete, а при программном формировании
файла — проверять тип и размер самостоятельно, поскольку штатные
проверки формы загрузки здесь не участвуют.
