Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Upload via Ajax in Yii2 not including files

Tags:

yii2

I'm trying to upload an image via Ajax request and seem to be hitting a similar issue as this question but it has no answer. I'm not sure if they are exactly the same so I'll post all the details I can here and hope the more detail helps someone find an answer.

I'm paraphrasing the below from my code (cutting out a lot of what I think is irrelevant to this question) so any typos you see are likely just me changing the code below.

The following is included in my model:

use yii\web\UploadedFile;

...

class Image extends \yii\db\ActiveRecord
{
    public $imageFile;

    public function rules()
    {
        return [
            [['imageFile'], 'file', 'skipOnEmpty' => true, 'extensions' => 'png, jpg'],
        ];
    }

    public function upload()
    {
        if ($this->validate()) {
            $this->imageFile->saveAs('uploads/' . $this->imageFile->baseName . '.' . $this->imageFile->extension);
            return true;
        } else {
            return false;
        }
    }

Here is a portion of my view file:

<?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]) ?>
<?= $form->field($model, 'title') ?>
<?= $form->field($model, 'imageFile')->fileInput() ?>
<?= Html::submitButton('Upload', ['class' => 'btn btn-success']) ?>
<?php ActiveForm::end(); ?>

And the Ajax in the view

$('form').on('beforeSubmit', function(e) {
    var form = $(this);
    $.post(form.attr('action'), form.serialize()).done(function(result) {
        console.log(result)
    });
    return false; // Prevent default form submit action
});

And the following in my controller

use yii\web\UploadedFile;

...

public function actionUpload()
{
    $model = new Image();

    if (Yii::$app->request->isAjax) {
        $model->load(Yii::$app->request->post());
        $model->imageFile = UploadedFile::getInstance($model, 'imageFile');
        if ($model->upload()) {
            $model->save(); // To save the title to the database
            Yii::$app->getSession()->addFlash('success', 'Image uploaded');
            return $this->redirect(['index']);
        }
    }

    Yii::$app->response->format = Response::FORMAT_JSON;
    return ['return' => 'just me testing here'];
}

The above Ajax will only ever save the title to the database and will not upload the file. If I transition everything to a standard post request I can get it all to work (title is saved to the database and image is uploaded to the proper directory). If I debug around the various views, models, and controllers, it looks like I'm just not getting the imageFile via the Ajax request. The $model->load(Yii::$app->request->post()); loads the title that was submitted via Ajax, so why does that not also pull the file? I thought maybe the $model->imageFiles = UploadedFile::getInstances($model, 'imageFiles'); would be the part that pulls the Ajax submitted files but it doesn't seem to get what I need either. As a test, I even tried treating the imageFile property as plain text and assigning it to another database property to be written and it worked just fine. So it seems it's being included in the model properly, just not being send with the Ajax submit.

Can anyone tell me how, in Yii2, I can submit the form over Ajax, including the selected file?

like image 585
justinvoelker Avatar asked Feb 08 '23 17:02

justinvoelker


1 Answers

The Problem

What the problem in ajax is that $_FILES details are not sent in asynchroous request. When we submit the filled form without ajax request and debug in backend side in PHP by

echo '<pre>';
print_r($_FILES); 
print_r($_POST); 
echo '</pre>';
die; 

then we successfuly get $_FILES and $_POST data.

But when we debug the same thing in ajax request, we get only $_POST values and we get $_FILES as NULL. This led us to conclusion that $_FILES data are not sent in ajax request by our above code.

The Solution

We need to use FormData of JavaScript.

What does it do?

In simple words, it adds all necessary information of file which needs to be uploaded to data param in $.ajax OR fill up $_FILES as well as all $_POST data ie non file input such as strings number etc.

In your view file

<?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]) ?>
<?= $form->field($model, 'title') ?>
<?= $form->field($model, 'imageFile')->fileInput() ?>    
<button type="button" class="btn btn-success subm">Upload</button>
<?php ActiveForm::end(); ?>

<script>
$('.subm').click(function(e){
    var formData = new FormData($('form')[0]);
    console.log(formData);
    $.ajax({
        url: "some_php_file.php",  //Server script to process data
        type: 'POST',

        // Form data
        data: formData,

        beforeSend: beforeSendHandler, // its a function which you have to define

        success: function(response) {
            console.log(response);
        },

        error: function(){
            alert('ERROR at PHP side!!');
        },


        //Options to tell jQuery not to process data or worry about content-type.
        cache: false,
        contentType: false,
        processData: false
    });
});
</script>

Testing

Now make ajax request and debug in PHP code by print_r() as shown above, you will notice that $_FILES is not NULL and its contains all file (which needs to be uploaded) data. And if it is set you can upload using move_uploaded_file() funtion

So this is how your file is uploaded via Ajax.

Referece 1

Referece 2

Referece 3

like image 105
Sohel Ahmed Mesaniya Avatar answered May 12 '23 05:05

Sohel Ahmed Mesaniya