Рано или поздно каждый разработчик сталкивается с необходимостью загружать файлы на сервер. Это может быть аватарка пользователя, документ PDF или галерея изображений. В Yii2 есть всё необходимое для комфортной работы с файлами, но есть и подводные камни. В этой статье разберём, как организовать загрузку и хранение файлов в Yii2, чтобы потом не пришлось краснеть перед заказчиком.

Digital-студия WNDER

Зачем вообще нужна отдельная логика для файлов?

Многие новички думают: «Что сложного? Взял файл из формы, сохранил в папку — и готово». Но в реальных проектах всё сложнее. Нужно проверить тип файла, ограничить размер, защититься от вредоносных скриптов, придумать уникальное имя, чтобы не перезаписать чужие данные, и позаботиться о том, чтобы файлы не потерялись при масштабировании.

В Yii2 загрузка файлов построена вокруг класса yii\web\UploadedFile. Он берёт на себя часть рутины: валидацию, сохранение, обработку ошибок. Но чтобы всё работало как часы, нужно правильно настроить модель и контроллер.

Простейшая загрузка: от формы до сервера

Начнём с классики — загрузки одного файла через форму. Допустим, мы делаем загрузку аватарки для пользователя.

Сначала создадим форму в представлении. Важно не забыть указать enctype="multipart/form-data", иначе файл не передастся.

<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;

$form = ActiveForm::begin([
    'options' => ['enctype' => 'multipart/form-data'],
]); ?>

<?= $form->field($model, 'avatar')->fileInput() ?>

<div class="form-group">
    <?= Html::submitButton('Загрузить', ['class' => 'btn btn-primary']) ?>
</div>

<?php ActiveForm::end(); ?>

Теперь модель. Здесь мы объявляем свойство $avatar и правило валидации для него.

namespace app\models;

use yii\base\Model;

class UploadForm extends Model
{
    public $avatar;

    public function rules()
    {
        return [
            [['avatar'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg, jpeg', 'maxSize' => 1024 * 1024 * 2],
        ];
    }
}

В контроллере обрабатываем форму: если валидация прошла, сохраняем файл.

public function actionUpload()
{
    $model = new UploadForm();

    if (Yii::$app->request->isPost) {
        $model->avatar = UploadedFile::getInstance($model, 'avatar');
        if ($model->validate()) {
            $path = Yii::getAlias('@webroot/uploads/') . $model->avatar->baseName . '.' . $model->avatar->extension;
            if ($model->avatar->saveAs($path)) {
                Yii::$app->session->setFlash('success', 'Файл загружен');
                return $this->redirect(['site/index']);
            }
        }
    }

    return $this->render('upload', ['model' => $model]);
}

Это базовый сценарий. Но обратите внимание: мы используем оригинальное имя файла. Если два пользователя загрузят файлы с одинаковым именем, второй перезапишет первый. Чтобы избежать этого, нужно генерировать уникальное имя.

Правило: Всегда генерируйте уникальное имя для загружаемого файла. Используйте хеш от времени или случайную строку. Например: Yii::$app->security->generateRandomString(16) . '.' . $file->extension.

Валидация файлов: что проверять и как не пропустить угрозу

Валидация — это ваш щит. Без неё на сервер может попасть что угодно: от гигабайтного видео до PHP-скрипта, замаскированного под картинку. В Yii2 валидатор file умеет проверять расширение, MIME-тип и размер. Но есть нюансы.

Стандартные правила выглядят так:

return [
    [['avatar'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg', 'maxSize' => 1024 * 1024 * 2, 'checkExtensionByMimeType' => true],
];

checkExtensionByMimeType — важный параметр. Если он включён (по умолчанию), Yii2 сверяет расширение файла с его MIME-типом. Это защищает от подмены: например, если вы разрешили только PNG, а пользователь загрузил PHP-скрипт с расширением .png, валидатор это заметит и отклонит.

Однако не полагайтесь только на расширение. MIME-тип тоже можно подделать, но это сложнее. Для максимальной безопасности используйте дополнительную проверку через функцию finfo_file() или библиотеку для работы с изображениями (например, getimagesize()).

Вот таблица с основными параметрами валидатора file:

Параметр Описание Пример
extensions Список разрешённых расширений 'png, jpg, gif'
maxSize Максимальный размер в байтах 1024 * 1024 * 2 (2 МБ)
checkExtensionByMimeType Сверять ли расширение с MIME-типом true (по умолчанию)
mimeTypes Разрешённые MIME-типы (можно ограничить) 'image/png, image/jpeg'

Помимо валидатора, всегда проверяйте, что файл действительно загружен через HTTP POST. В Yii2 это делает UploadedFile::getInstance() — он возвращает null, если файл не был загружен.

Хранение файлов: папки, база данных или облако?

Когда файл загружен, его нужно где-то хранить. Самый простой вариант — локальная файловая система. Но и здесь есть простор для творчества.

Структура папок. Не сваливайте все файлы в одну кучу. Если у вас тысячи изображений, папка с ними станет неповоротливой. Разбивайте по датам или по ID пользователя. Например: /uploads/2025/04/15/abc123.jpg. Это упростит поиск и резервное копирование.

Имена файлов. Как мы уже говорили, генерируйте уникальные имена. Можно использовать хеш от содержимого файла (например, md5_file()) — это гарантирует уникальность и позволит избежать дубликатов. Но учтите, что хеш от большого файла вычисляется не мгновенно.

Права доступа. Папка для загрузок должна быть доступна для записи веб-серверу, но не должна позволять выполнение скриптов. Если вы используете Apache, добавьте в .htaccess в папке загрузок строку php_flag engine off или запретите выполнение PHP через <FilesMatch \.php$> Deny from all </FilesMatch>.

База данных или файловая система? Иногда спрашивают: может, хранить файлы в БД (BLOB)? Технически можно, но не рекомендуется для больших объёмов. Файловая система быстрее, проще масштабируется и не раздувает базу. БД лучше использовать для метаданных: имя файла, размер, MIME-тип, путь.

Вот сравнение подходов к хранению:

Подход Плюсы Минусы
Локальная ФС Простота, скорость, дешевизна Сложно масштабировать, нужен бэкап
База данных (BLOB) Целостность, транзакции Раздувание БД, медленный доступ
Облако (S3, CDN) Масштабируемость, отказоустойчивость Стоимость, зависимость от интернета

Для большинства проектов на Yii2 достаточно локальной ФС с разумной структурой. Если проект растёт, можно подключить облачное хранилище через расширения (например, yii2-aws-s3).

Множественная загрузка и работа с несколькими файлами

Часто нужно загрузить не один файл, а несколько — например, фотографии товара. В Yii2 это делается с помощью UploadedFile::getInstances().

В представлении используйте fileInput(['multiple' => true]). В модели объявите массив:

public $gallery;

public function rules()
{
    return [
        [['gallery'], 'file', 'skipOnEmpty' => true, 'extensions' => 'png, jpg', 'maxFiles' => 5],
    ];
}

В контроллере получите все файлы и обработайте каждый в цикле:

$model->gallery = UploadedFile::getInstances($model, 'gallery');
if ($model->validate()) {
    foreach ($model->gallery as $file) {
        $filename = Yii::$app->security->generateRandomString(16) . '.' . $file->extension;
        $file->saveAs(Yii::getAlias('@webroot/uploads/') . $filename);
    }
}

Не забудьте про ограничение maxFiles, чтобы пользователь не загрузил сотню файлов и не положил сервер.

Итог: что запомнить

Работа с файлами в Yii2 — это не магия, а последовательность простых шагов. Главное — не лениться и делать всё по правилам:

  • Всегда используйте UploadedFile и валидатор file.
  • Генерируйте уникальные имена файлов.
  • Проверяйте MIME-тип и расширение.
  • Организуйте папки в понятную структуру.
  • Запретите выполнение скриптов в папке загрузок.

Следуя этим советам, вы избежите большинства проблем с безопасностью и потерянными файлами. А если проект вырастет, вы будете готовы к переходу на облачное хранилище.

Удачи в разработке!

Студия WNDER