input-file-upload.md 6.22 KB
Newer Older
1 2
Uploading Files
===============
3

4 5
> Note: This section is under development.

6
Uploading files in Yii is done via the a form model, its validation rules and some controller code. Let's review what's needed
7 8 9 10 11
to handle uploads properly.

Form model
----------

12
First of all, you need to create a model that will handle file uploads. Create `models/UploadForm.php` with the following
13
content:
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

```php
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|Null file attribute
     */
    public $file;

    /**
     * @return array the validation rules.
     */
    public function rules()
    {
        return [
            [['file'], 'file'],
        ];
    }
}
```

43 44 45 46 47
In the code above, we 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
---------
48

49
Next, create a view that will render the form:
50

51 52 53 54 55 56 57 58 59 60 61 62 63
```php
<?php
use yii\widgets\ActiveForm;

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

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

<button>Submit</button>

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

64
The `'enctype' => 'multipart/form-data'` is necessary because it allows file uploads. `fileInput()` represents a form
65
input field.
66

67 68 69
Controller
----------

70
Now create the controller that connects the form and model together:
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97

```php
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->validate()) {                
                $model->file->saveAs('uploads/' . $model->file->baseName . '.' . $model->file->extension);
            }
        }

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

99 100
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:
101

102
```php
103
$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE //in the code framework
104 105
```

106
If validation is successful, then we're saving the file: 
107

108 109 110 111
```php
$model->file->saveAs('uploads/' . $model->file->baseName . '.' . $model->file->extension);
```

112
If you're using the "basic" application template, then folder `uploads` should be created under `web`.
113

114
That's it. Load the page and try uploading. Uploads should end up in `basic/web/uploads`.
115

116 117
Additional information
----------------------
118 119 120

### Required rule

121
If you need to make the file upload mandatory, use `skipOnEmpty` like the following:
122

123 124 125 126 127 128 129 130 131 132 133
```php
public function rules()
{
    return [
        [['file'], 'file', 'skipOnEmpty' => false],
    ];
}
```

### MIME type

134
It is wise to validate the type of file uploaded. FileValidator has the property `$extensions` for this purpose:
135

136 137 138 139
```php
public function rules()
{
    return [
Vladimir committed
140
        [['file'], 'file', 'extensions' => 'gif, jpg',],
141 142 143
    ];
}
```
144

145
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`:
146 147 148 149 150

```php
public function rules()
{
    return [
151
        [['file'], 'file', 'extensions' => 'jpg, png', 'mimeTypes' => 'image/jpeg, image/png',],
152 153 154 155
    ];
}
```

156 157 158 159 160
[List of common media types](http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_types)

### Validating uploaded image

If you upload an image, [[yii\validators\ImageValidator|ImageValidator]] may come in handy. It verifies if an attribute
161
received a valid image that can be then either saved or processed using the [Imagine Extension](https://github.com/yiisoft/yii2/tree/master/extensions/imagine).
162

163
### Uploading multiple files
164

165 166 167
If you need to download multiple files at once, some adjustments are required. 

View:
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188

```php
<?php
use yii\widgets\ActiveForm;

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

if ($model->hasErrors()) { //it is necessary to see all the errors for all the files.
    echo '<pre>';
    print_r($model->getErrors());
    echo '</pre>';
}
?>

<?= $form->field($model, 'file[]')->fileInput(['multiple' => '']) ?>

    <button>Submit</button>

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

189
The difference is the following line:
190

191 192 193
```php
<?= $form->field($model, 'file[]')->fileInput(['multiple' => '']) ?>
```
194

195
Controller:
196

197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
```php
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) {

            $files = UploadedFile::getInstances($model, 'file');

            foreach ($files as $file) {

                $_model = new UploadForm();

                $_model->file = $file;

                if ($_model->validate()) {
                    $_model->file->saveAs('uploads/' . $_model->file->baseName . '.' . $_model->file->extension);
                } else {
                    foreach ($_model->getErrors('file') as $error) {
                        $model->addError('file', $error);
                    }
                }
            }

            if ($model->hasErrors('file')){
                $model->addError(
                    'file',
                    count($model->getErrors('file')) . ' of ' . count($files) . ' files not uploaded'
                );
            }

        }

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

244
The difference is using `UploadedFile::getInstances($model, 'file');` instead of `UploadedFile::getInstance($model, 'file');`.
245
The former returns instances for **all** uploaded files while the latter gives you only a single instance.