Yii2: Загрузка изображений и удаление через AJAX
Давайте рассмотрим как в Yii2 прикреплять изображения к статье, обновлять их и удалять через AJAX. Я предполагаю что у вас уже сгенерированн CRUD для ваших задач.
Для начала создадим новую ячейку в БД в моём случае это бует таблица posts и ячейку я назову "logo". Тип Varchar длина 512, allow null.
Модель
В модели необходимо создать публичную переменную в которую мы будем передавать загружаемое изображение. Добвааляем её в класс модели models/Posts.php:
class Posts extends \yii\db\ActiveRecord // Ваш класс модели
{
public $file; // Добавляем переменную
Далее в правилах валидации добавим правило для нашего файла: [['file'], 'file'], В моём случае набор правил будет выглядеть вот так:
public function rules()
{
return [
[['title', 'slug', 'content'], 'required'],
[['content'], 'string'],
[['title', 'slug'], 'string', 'max' => 255],
[['logo', 'img'], 'string', 'max' => 512],
[['slug'], 'unique'],
[['file'], 'file'], // Добавленное правило валидации
];
}
Представление
Теперь давайте перейдём в наше представление views/posts/_form.php и в самом верху подключим хелперы:
use yii\helpers\Html;
use yii\helpers\Url;
Будьте внимательны, если вы создавали ваш CRUD не в ручную а например при помощи Gii то скорей всего хелпер yii\helpers\Html; у вас уже подключен и подключать его повторно не следует.
В параметрах формы необходимо указать что мы будем передавать файлы:
<?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]); ?>
Теперь давайте создадим поле для загрузки изображения. Обратите внимание поле будет передавать данные в ранее созданую нами публичную переменную $file в модели Posts что бы затем контроллер мог сохранить отдельно файл в папку на сервере и записать имя файла в объект, а затем сохранить его в БД.
<?= $form->field($model, 'file')->fileInput() ?>
Далее прямо над полем для загрузки размещаем небольшой виджет который будет отображать загруженное изображение при просмотре статьи:
<?php if(!empty($model->logo)){
echo Html::img($model->logo, $options = ['class' => 'postImg', 'style' => ['width' => '180px']]);
} ?>
Давайте разберёмся что тут у нас происходит? Данный виджет будет виден только при редактировании уже ранее созданой статьи и только в том случае, если к статье уже было ранее добавленно изображение: if(!empty($model->logo)). Далее мы вызываем html хелпер Html::img который будет выдавать код типа <img src> и с качестве первого параметра принимает путь к файлу: Html::img($model->logo как раз наше изображение. Второй параметр у нас отвечает за размер изображения.
Контроллер
Для начала давайте подключим недостающий хелпер который занимается загрузкой файлов в Yii2:
use yii\web\UploadedFile;
Теперь необходимо в двух экшенах контроллера (созжание и обновление) необходимо добавить обработку передаваемого файла изображения. Для этого в экшенах public function actionCreate и public function actionUpdate сразу после строчки: if ($model->load(Yii::$app->request->post()) && $model->save()) { добавляем вот такой код:
$imageName = time();
$model->file = UploadedFile::getInstance($model, 'file');
if(!empty($model->file))
{
$model->file->saveAs('uploads/blog_'.$imageName.'.'.$model->file->extension);
$model->logo = 'uploads/blog_'.$imageName.'.'.$model->file->extension;
$model->save();
}
Давайте разберём его. В данном методе $imageName = time(); я получаю текущий тайм штам типа 1486298642 я буду использовать его как часть имени файла, что бы избежать конфликта одинаковых имён на сервере. Далее мы передаём в наш объект модели наш файл полученный из формы: $model->file = UploadedFile::getInstance($model, 'file'); через метод хелпер UploadedFile. Далее задаём условие, если файл загружен if(!empty($model->file)) сохраняем его в папку uploads в публичной директории сервера: $model->file->saveAs('uploads/blog_'.$imageName.'.'.$model->file->extension); затем к ранее созданному нами штампу добавляем префикс blog_ в результате загруженные файлы будут иметь имена вида blog_486298642 и затем добавляем точку и расширение оригинального файла '.'.$model->file->extension.
Теперь обратите внимание мы наконец отходим от работы с самим файлом который ранее хранился в переменной $file и передаём его имя и путь в объект модели в виде строки: $model->logo = 'uploads/blog_'.$imageName.'.'.$model->file->extension; Собственно осталось только сохранить данное состояние объекта $model->save(); что бы данные записались в БД. Это акупльно как в случае создания, так и обновления материала. Давайте ещё раз вернёмся на три строки выше к условию if(!empty($model->file)) мы его создали на тот случай, когда при обновлении материала мы не меняем текущее изображение или его вовсе нет. Без данного условия мы-бы получили ошибку о попытке передать пустой объект.
Удаление изображения через AJAX
Теперь давайте реализуем возможность удаления прикреплённого изображения без перезагрузки страницы, что весма полезно при обновлении материалов. Для начала, здесь же в контроллере создадим новый метод, который мы в дальнейшем и будем вызывать через AJAX запрос. Привожу полный код экшена:
public function actionDeleteimage($id)
{
$model = $this->findModel($id);
$imgName = $model->logo;
unlink(Yii::getAlias('').$imgName);
$model->logo = null;
$model->update();
if (Yii::$app->request->isAjax)
{
return 'Deleted';
} else {
return $this->redirect(['update', 'id' => $model->id]);
}
}
Как видно из кода в качестве агрумента мы передаём ID объекта, далее создаём переменные с индификатором $model = $this->findModel($id); и путь до файла $imgName = $model->logo; Которые получаем из экземпляра объекта. Затем вызываем стандартный метод php - unlink() с параметром пути, который удаляет файл на сервере. Далее присваиваем экземпляру объекта значение null в для изображения и обновляем экземпляр модели $model->update(); в случае ели всё прошло успешно возвращяем строку Deleted которая будет отображена в форме.
Терерь давайте вернёмся в представление и в нашей форме внутри виджета <?php if(!empty($model->logo)){ сразу после хелпера с изображением, что мы ранее создали вставляем AJAX ссылку которая будут обращатся к нашему экшену удаления в контроллере, который мы только что создали. Итак довайте добавим наконец наш код:
echo Html::a('<span class="glyphicon glyphicon-trash"></span>', ['posts/deleteimage', 'id' => $model->id], [
'onclick'=>
"$.ajax({
type:'POST',
cache: false,
url: '".Url::to(['posts/deleteimage', 'id' => $model->id])."',
success : function(response) {
$('.link-del').html(response);
$('.postImg').remove();
}
});
return false;
$(".postImg").remove(); // Удалить превью картинки
",
'class' => 'link-del'
]);
Как видно из кода мы снова прибегли к помощи html хелпера для создания ссылки с AJAX запросом. Далее мы вызываем роутер которвый обращается к экшену удаления ['posts/deleteimage' и передаём переменную с id нашего объекта 'id' => $model->id]. Теперь после клика по ссылке отработает наш экшен на удаление и вернёт там ответ. В случае успеха сработает вот эта интересная часть AJAX:
success : function(response) {
$('.link-del').html(response);
$('.postImg').remove();
}
Первая строка получит return из экшена где мы написали Deleted и отобразит его на экране, а вторая строка уберёт элемент картинки из DOM страницы.
Вот собственно и всё, теперь вы можете не засорять ваш сервер уже несуществующими изображениями.
P.S.
Это черновик статьи написвнный на скорую руку для закрепления только что проделанного опыта. Если у вас возникнут вопросы из за плохого изложения материала, или предложения по улучшению пожалуйста оставьте ваши комментарии.