input-file-upload.md 5.69 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

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

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

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

```php
public function rules()
{
    return [
        [['file'], 'image', 'mimeTypes' => 'image/jpeg, image/png',],
    ];
}
```

159
### Uploading multiple files
160

161
If you need download multiple files at once some adjustments are required. View:
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182

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

183
The difference is the following line:
184

185 186 187
```php
<?= $form->field($model, 'file[]')->fileInput(['multiple' => '']) ?>
```
188

189
Controller:
190

191 192 193 194 195 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
```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]);
    }
}
```
237

238 239
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.