Uploading Files
Uploading files in Yii is done via the a form model, its validation rules and some controller code. Let's review what's required to handle uploads properly.
Uploading single file
First of all, you need to create a model that will handle file uploads. Create models/UploadForm.php
with the following
content:
namespace app\models;
use yii\base\Model;
use yii\web\UploadedFile;
/**
* UploadForm is the model behind the upload form.
*/
class UploadForm extends Model
{
/**
* @var UploadedFile file attribute
*/
public $file;
/**
* @return array the validation rules.
*/
public function rules()
{
return [
[['file'], 'file'],
];
}
}
In the code above, we've created a model UploadForm
with an attribute $file
that will become <input type="file">
in
the HTML form. The attribute has the validation rule named file
that uses [[yii\validators\FileValidator|FileValidator]].
Form view
Next, create a view that will render the form:
<?php
use yii\widgets\ActiveForm;
?>
<?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]) ?>
<?= $form->field($model, 'file')->fileInput() ?>
<button>Submit</button>
<?php ActiveForm::end() ?>
The 'enctype' => 'multipart/form-data'
is necessary because it allows file uploads. fileInput()
represents a form
input field.
Controller
Now create the controller that connects the form and model together:
namespace app\controllers;
use Yii;
use yii\web\Controller;
use app\models\UploadForm;
use yii\web\UploadedFile;
class SiteController extends Controller
{
public function actionUpload()
{
$model = new UploadForm();
if (Yii::$app->request->isPost) {
$model->file = UploadedFile::getInstance($model, 'file');
if ($model->file && $model->validate()) {
$model->file->saveAs('uploads/' . $model->file->baseName . '.' . $model->file->extension);
}
}
return $this->render('upload', ['model' => $model]);
}
}
Instead of model->load(...)
, we are using UploadedFile::getInstance(...)
. [[\yii\web\UploadedFile|UploadedFile]]
does not run the model validation, rather it only provides information about the uploaded file. Therefore, you need to run the validation manually via $model->validate()
to trigger the [[yii\validators\FileValidator|FileValidator]] that expects a file:
$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE //in the code framework
If validation is successful, then we're saving the file:
$model->file->saveAs('uploads/' . $model->file->baseName . '.' . $model->file->extension);
If you're using the "basic" application template, then folder uploads
should be created under web
.
That's it. Load the page and try uploading. Uploads should end up in basic/web/uploads
.
Validation
It's often required to adjust validation rules to accept certain files only or require uploading. Below we'll review some common rule configurations.
Required
If you need to make the file upload mandatory, use skipOnEmpty
like the following:
public function rules()
{
return [
[['file'], 'file', 'skipOnEmpty' => false],
];
}
MIME type
It is wise to validate the type of file uploaded. FileValidator has the property $extensions
for this purpose:
public function rules()
{
return [
[['file'], 'file', 'extensions' => 'gif, jpg',],
];
}
Keep in mind that only the file extension will be validated, but not the actual file content. In order to validate the content as well, use the mimeTypes
property of FileValidator
:
public function rules()
{
return [
[['file'], 'file', 'extensions' => 'jpg, png', 'mimeTypes' => 'image/jpeg, image/png',],
];
}
Image properties
If you upload an image, [[yii\validators\ImageValidator|ImageValidator]] may come in handy. It verifies if an attribute received a valid image that can be then either saved or processed using the Imagine Extension.
Uploading multiple files
If you need to upload multiple files at once, some adjustments are required.
Model:
class UploadForm extends Model
{
/**
* @var UploadedFile|Null file attribute
*/
public $file;
/**
* @return array the validation rules.
*/
public function rules()
{
return [
[['file'], 'file', 'maxFiles' => 10], // <--- here!
];
}
}
View:
<?php
use yii\widgets\ActiveForm;
$form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]);
?>
<?= $form->field($model, 'file[]')->fileInput(['multiple' => true]) ?>
<button>Submit</button>
<?php ActiveForm::end(); ?>
The difference is the following line:
<?= $form->field($model, 'file[]')->fileInput(['multiple' => true]) ?>
Controller:
namespace app\controllers;
use Yii;
use yii\web\Controller;
use app\models\UploadForm;
use yii\web\UploadedFile;
class SiteController extends Controller
{
public function actionUpload()
{
$model = new UploadForm();
if (Yii::$app->request->isPost) {
$model->file = UploadedFile::getInstances($model, 'file');
if ($model->file && $model->validate()) {
foreach ($model->file as $file) {
$file->saveAs('uploads/' . $file->baseName . '.' . $file->extension);
}
}
}
return $this->render('upload', ['model' => $model]);
}
}
There are two differences from single file upload. First is that UploadedFile::getInstances($model, 'file');
used
instead of UploadedFile::getInstance($model, 'file');
. The former returns instances for all uploaded files while
the latter gives you only a single instance. The second difference is that we're doing foreach
and saving each file.