Загрузка файлов (изображений) на сервер в 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.
Предыдущая запись
Добавление (регистрация) мета-тегов в Yii2Следующая запись
Yii2: Отправка данных асинхронно, AJAX загрузка файла