Загрузка файлов (изображений) на сервер в Yii2
В этой статье я покажу несколько примеров (от простого к сложному) как можно реализовать загрузку файлов на сервер и их удаление. Вообще про загрузку файлов в Yii2 можно почитать в документации , здесь же я покажу примеры реализации.
Простая загрузка файла
Загрузка одного файла
Класс UploadFileForm
class UploadFileForm extends Model { /** * @var UploadedFile */ public $file; public function rules() { return [ ['file', 'image', 'extensions' => ['jpg', 'jpeg', 'png', 'gif'], 'checkExtensionByMimeType' => true, 'maxSize' => 512000, // 500 килобайт = 500 * 1024 байта = 512 000 байт 'tooBig' => 'Limit is 500KB' ], ]; } public function upload() { if ($this->validate()) { $dir = 'uploads/'; // Директория - должна быть создана $name = $this->randomFileName($this->file->extension); $file = $dir . $name; $this->file->saveAs($file); // Сохраняем файл return true; } else { return false; } } private function randomFileName($extension = false) { $extension = $extension ? '.' . $extension : ''; do { $name = md5(microtime() . rand(0, 1000)); $file = $name . $extension; } while (file_exists($file)); return $file; } }
Представление:
<?php /* @var $this yii\web\View */ /* @var $model \frontend\forms\UploadFileForm */ use yii\widgets\ActiveForm; ?> <?php $form = ActiveForm::begin(['options' => []]) ?> <?= $form->field($model, 'file')->fileInput() ?> <button>Submit</button> <?php ActiveForm::end() ?>
Контроллер:
public function actionOneFile() { $model = new UploadFileForm(); if (Yii::$app->request->isPost) { $model->file = UploadedFile::getInstance($model, 'file'); if ($model->upload()) { Yii::$app->session->setFlash('success', 'Изображение загружено'); return $this->refresh(); } } return $this->render('index', ['model' => $model]); }
Можно получить файл не из модели. Например, получить файл при отправке запроса по API:
$file = UploadedFile::getInstanceByName('file');
Загрузка нескольких файлов
Класс UploadFilesForm
class UploadFilesForm extends Model { /** * @var UploadedFile[] */ public $files; public function rules() { return [ ['files', 'image', 'extensions' => ['jpg', 'jpeg', 'png', 'gif'], 'checkExtensionByMimeType' => true, 'maxSize' => 512000, // 500 килобайт = 500 * 1024 байта = 512 000 байт 'tooBig' => 'Limit is 500KB', 'maxFiles' => 5 ] ]; } public function upload() { if ($this->validate()) { foreach ($this->files as $file) { $file->saveAs('uploads/' . $this->randomFileName($file->extension)); } return true; } else { return false; } } private function randomFileName($extension = false) { $extension = $extension ? '.' . $extension : ''; do { $name = md5(microtime() . rand(0, 1000)); $file = $name . $extension; } while (file_exists($file)); return $file; } }
Представление:
<?php use yii\widgets\ActiveForm; /* @var $this yii\web\View */ /* @var $model \frontend\forms\UploadFileForm */ ?> <?php $form = ActiveForm::begin(['options' => []]) ?> <?= $form->field($model, 'files[]')->fileInput(['multiple' => true, 'accept' => 'image/*']) ?> <button>Submit</button> <?php ActiveForm::end() ?>
Контроллер:
public function actionMultiFile() { $model = new UploadFilesForm(); if (Yii::$app->request->isPost) { $model->files = UploadedFile::getInstances($model, 'files'); if ($model->upload()) { Yii::$app->session->setFlash('success', 'Изображения загружены'); return $this->refresh(); } } return $this->render('index', ['model' => $model]); }
Изображение для новости
В данном примере мы немного отойдём от привычной для новичков работы с моделями. Мы разделим нашу сущность (в данном случае это новость) на class NewsForm extends Model
и class News extends ActiveRecord
. NewsForm принимает данные и валидирует их. News работает с базой данных.
Миграция:
public function up() { $tableOptions = null; if ($this->db->driverName === 'mysql') { $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; } $this->createTable('{{%news}}', [ 'id' => $this->primaryKey(), 'name' => $this->string()->notNull()->unique(), 'content' => $this->text(), 'slug' => $this->string()->notNull()->unique(), 'image' => $this->string()->defaultValue(''), 'created_at' => $this->integer()->notNull(), ], $tableOptions); $this->createIndex('{{%idx-new-slug}}', '{{%news}}', 'slug', true); } public function down() { $this->dropTable('{{%news}}'); }
Класс NewsForm
:
class NewsForm extends Model{ /** * @var UploadedFile * Здесь хранится экземпляр класса UploadedFile */ public $image; public $name; public $content; public $slug; public $created_at; private $_model; public function __construct(News $model = null, $config = []) { if ($model) { $this->name = $model->name; $this->content = $model->content; $this->slug = $model->slug; $this->created_at = $model->created_at; $this->_model = $model; } parent::__construct($config); } public function rules() { return [ [['name'], 'required'], [['name', 'slug'], 'string', 'max' => 255], ['content', 'string'], ['slug', SlugValidator::class], [['name', 'slug'], 'unique', 'targetClass' => News::class, 'filter' => $this->_model ? ['<>', 'id', $this->_model->id] : null ], [['image'], 'image', 'extensions' => ['jpg', 'jpeg', 'png', 'gif'], 'checkExtensionByMimeType' => true, 'maxSize' => 512000, // 500 килобайт = 500 * 1024 байта = 512 000 байт 'tooBig' => 'Limit is 500KB' ], ]; } public function uploadImage(UploadedFile $image, $currentImage = null) { if (!is_null($currentImage)) $this->deleteCurrentImage($currentImage); $this->image = $image; if($this->validate()) return $this->saveImage(); return false; } private function getUploadPath() { return Yii::$app->params['uploadPath'] . 'news/'; } /** * @return string */ public function generateFileName(): string { do { $name = substr(md5(microtime() . rand(0, 1000)), 0, 20); $file = strtolower($name .'.'. $this->image->extension); } while (file_exists($file)); return $file; } public function deleteCurrentImage($currentImage) { if ($currentImage && $this->fileExists($currentImage)) { unlink($this->getUploadPath() . $currentImage); } } /** * @param $currentFile * @return bool */ public function fileExists($currentFile): bool { $file = $currentFile ? $this->getUploadPath() . $currentFile : null; return file_exists($file); } /** * @return string */ public function saveImage(): string { $filename = $this->generateFilename(); $this->image->saveAs($this->getUploadPath() . $filename); return $filename; } }
Конфигурация путей (*/config/params.php):
// ... 'uploadHostInfo' => 'http://mysite.local/upload', // Показываем отсюда 'uploadPath' => dirname(__DIR__, 2) . '/frontend/web/upload/', // Загружаем сюда
Класс News
:
class News extends ActiveRecord { public static function tableName() { return '{{%news}}'; } public function getImagePath() { if ($this->image) return $this->getImage($this->image); return 'https://via.placeholder.com/300x200'; // Default image } private function getImage(string $filename): string { return Yii::$app->params['uploadHostInfo'] . 'news/' . $filename; } public function beforeDelete() { $this->deleteImage(); return parent::beforeDelete(); } public function deleteImage() { $form = new NewsForm(); $form->deleteCurrentImage($this->image); } }
Виды
_form.php
<?php use yii\helpers\Html; use yii\widgets\ActiveForm; /* @var $this yii\web\View */ /* @var $model common\models\News */ /* @var $modelForm \common\forms\NewsForm */ /* @var $form yii\widgets\ActiveForm */ ?> <div class="news-form"> <?php $form = ActiveForm::begin(['id' => 'news-form', 'options' => []]); ?> <?= $form->field($modelForm, 'name')->textInput(['maxlength' => true]) ?> <?= $form->field($modelForm, 'content')->textarea(['rows' => 10]) ?> <?= $form->field($modelForm, 'slug')->textInput(['maxlength' => true]) ?> <?= $form->field($modelForm, 'created_at')->textInput(['maxlength' => true]) ?> <div class="new-image"> <img src="<?= $model->getImagePath() ?>" alt="" width="300"> </div> <?= $form->field($modelForm, 'image')->fileInput() ?> <div class="form-group"> <?= Html::submitButton('Save', ['class' => 'btn btn-default']) ?> </div> <?php ActiveForm::end(); ?> </div>
create.php
<?php use yii\helpers\Html; /* @var $this yii\web\View */ /* @var $modelForm \common\forms\NewsForm */ /* @var $model \common\models\News */ $this->title = 'Create New'; $this->params['breadcrumbs'][] = ['label' => 'News', 'url' => ['index']]; $this->params['breadcrumbs'][] = $this->title; ?> <div class="new-create"> <h1><?= Html::encode($this->title) ?></h1> <?= $this->render('_form', [ 'model' => $model, 'modelForm' => $modelForm ]) ?> </div>
index.php
<?php use common\models\News; use yii\helpers\Html; use yii\grid\GridView; use yii\widgets\Pjax; /* @var $this yii\web\View */ /* @var $dataProvider yii\data\ActiveDataProvider */ $this->title = 'News'; $this->params['breadcrumbs'][] = $this->title; ?> <div class="new-index"> <h1><?= Html::encode($this->title) ?></h1> <?php Pjax::begin(); ?> <p> <?= Html::a('Create New', ['create'], ['class' => 'btn btn-success']) ?> </p> <?= GridView::widget([ 'dataProvider' => $dataProvider, 'columns' => [ ['class' => 'yii\grid\SerialColumn'], 'name', [ 'value' => function (News $model) { return Html::img($model->getImagePath(), ['width' => 100, 'alt' => $model->name]); }, 'label' => 'Image', 'format' => 'raw' ], ['class' => 'yii\grid\ActionColumn'], ] ]); ?> <?php Pjax::end(); ?> </div>
update.php
<?php use yii\helpers\Html; /* @var $this yii\web\View */ /* @var $modelForm \common\forms\NewsForm */ /* @var $model \common\models\News */ $this->title = 'Update New: ' . $model->name; $this->params['breadcrumbs'][] = ['label' => 'News', 'url' => ['index']]; $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->id]]; $this->params['breadcrumbs'][] = 'Update'; ?> <div class="new-update"> <h1><?= Html::encode($this->title) ?></h1> <?= $this->render('_form', [ 'model' => $model, 'modelForm' => $modelForm ]) ?> </div>
view.php
<?php use common\models\News; use yii\helpers\Html; use yii\widgets\DetailView; /* @var $this yii\web\View */ /* @var $model common\models\News */ $this->title = $model->name; $this->params['breadcrumbs'][] = ['label' => 'News', 'url' => ['index']]; $this->params['breadcrumbs'][] = $this->title; \yii\web\YiiAsset::register($this); ?> <div class="new-view"> <h1><?= Html::encode($this->title) ?></h1> <p> <?= Html::a('Update', ['update', 'id' => $model->id], ['class' => 'btn btn-primary']) ?> | <?= Html::a('Delete', ['delete', 'id' => $model->id], [ 'class' => 'btn btn-danger', 'data' => [ 'confirm' => 'Are you sure you want to delete this item?', 'method' => 'post' ] ]) ?> <?= Html::a('Delete Image', ['delete-image', 'id' => $model->id], [ 'class' => 'btn btn-danger', 'data' => [ 'confirm' => 'Are you sure you want to delete this item?', 'method' => 'post' ] ]) ?> </p> <?= DetailView::widget([ 'model' => $model, 'attributes' => [ 'id', 'name', 'content:raw', 'slug', 'created_at:date', [ 'value' => function (News $model) { return Html::img($model->getImagePath(), ['width' => 200, 'alt' => $model->name]); }, 'label' => 'Image', 'format' => 'raw' ] ] ]) ?> </div>
Контроллер:
// Создание новости public function actionCreate() { $model = new News(); $modelForm = new NewsForm(); if ($modelForm->load(Yii::$app->request->post()) && $modelForm->validate()) { if ($image = UploadedFile::getInstance($modelForm, 'image')) { $model->image = $modelForm->uploadImage($image); } $model->name = $modelForm->name; $model->slug = $modelForm->slug ?: Inflector::slug($model->name); $model->created_at = $modelForm->created_at ?: time(); if($model->save()) { return $this->redirect(['view', 'id' => $model->id]); } } return $this->render('create', [ 'model' => $model, 'modelForm' => $modelForm ]); } // Редактирование новости public function actionUpdate($id) { $model = $this->findModel($id); $modelForm = new NewsForm($model); if ($modelForm->load(Yii::$app->request->post()) && $modelForm->validate()) { if ($image = UploadedFile::getInstance($modelForm, 'image')) { $model->image = $modelForm->uploadImage($image, $model->image); } $model->name = $modelForm->name; $model->slug = $modelForm->slug ?: Inflector::slug($model->name); if($model->save()) { Yii::$app->session->setFlash('success', 'Все прошло удачно'); return $this->redirect(['view', 'id' => $model->id]); } } return $this->render('update', [ 'model' => $model, 'modelForm' => $modelForm, ]); } // Удаление изображения public function actionDeleteImage($id) { $model = $this->findModel($id); if (Yii::$app->request->isPost) { $model->deleteImage(); $model->image = ''; if($model->save()) { Yii::$app->session->setFlash('success', 'Изображение удалено!'); } } return $this->render('view', [ 'model' => $model, ]); }
При загрузке изображения можно автоматически обрезать его при помощи класса yii\imagine\Image.