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

4 5
> Note: This section is under development.

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

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

First of all, you need to create a model that will handle file upload. Create `models/UploadForm.php` with the following
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 65
The `'enctype' => 'multipart/form-data'` is important since it allows file uploads. `fileInput()` represents a form
input field.
66

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

Now create the controller that connects 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 101 102
Instead of `model->load(...)` we are using `UploadedFile::getInstance(...)`. [[\yii\web\UploadedFile|UploadedFile]] 
does not run the model validation. It only provides information about the uploaded file. Therefore, you need to run
validation manually via `$model->validate()`. This triggers the [[yii\validators\FileValidator|FileValidator]] that
expects a file:
103

104 105 106 107
```php
$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE //in code framework
```

108
If validation is successful, then we're saving the file: 
109

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

114
If you're using "basic" application template then folder `uploads` should be created under `web`.
115

116
That's it. Load the page and try uploading. Uplaods should end up in `basic/web/uploads`.
117

118 119
Additional information
----------------------
120 121 122

### Required rule

123
If you need to make file upload mandatory use `skipOnEmpty` like the following:
124

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

### MIME type

Vladimir committed
136
It is wise to validate type of the file uploaded. FileValidator has property `$extensions` for the purpose:
137

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

147
The thing is that it validates only file extension and not the file content. In order to validate content as well use
148
`mimeTypes` property of `FileValidator`:
149 150 151 152 153

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

159 160 161 162 163 164 165
[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
received a valid image that can be then either saved or processed using [Imagine Extension](https://github.com/yiisoft/yii2/tree/master/extensions/imagine).

166
### Uploading multiple files
167

168
If you need download multiple files at once some adjustments are required. View:
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189

```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(); ?>
```

190
The difference is the following line:
191

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

196
Controller:
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 243
```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]);
    }
}
```
244

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