jQuery.ajax ile multipart/formdata gönderme

jQuery'nin ajax fonksiyonunu kullanarak sunucu tarafındaki bir PHP betiğine dosya gönderirken sorun yaşıyorum. Dosya Listesini $('#fileinput').attr('files') ile almak mümkün, ancak bu Veriyi sunucuya göndermek nasıl mümkün olabilir? Dosya girişi kullanıldığında sunucu tarafındaki php komut dosyasında elde edilen dizi ($_POST) 0'dır (NULL).

Bunun mümkün olduğunu biliyorum (ancak şimdiye kadar herhangi bir jQuery çözümü bulamadım, sadece Prototye kodu (http://webreflection.blogspot.com/2009/03/safari-4-multiple-upload-with-progress.html)).

Bu nispeten yeni gibi görünüyor, bu yüzden lütfen XHR/Ajax ile dosya yüklemenin imkansız olduğundan bahsetmeyin, çünkü kesinlikle çalışıyor.

Safari 5'teki işlevselliğe ihtiyacım var, FF ve Chrome güzel olurdu ama gerekli değil.

Şimdilik kodum şu:

$.ajax({
    url: 'php/upload.php',
    data: $('#file').attr('files'),
    cache: false,
    contentType: 'multipart/form-data',
    processData: false,
    type: 'POST',
    success: function(data){
        alert(data);
    }
});
Çözüm

Safari 5/Firefox 4 ile başlayarak, FormData sınıfını kullanmak en kolay yoldur:

var data = new FormData();
jQuery.each(jQuery('#file')[0].files, function(i, file) {
    data.append('file-'+i, file);
});

Artık XMLHttpRequest ile birlikte gönderilmeye hazır bir FormData nesneniz var.

jQuery.ajax({
    url: 'php/upload.php',
    data: data,
    cache: false,
    contentType: false,
    processData: false,
    method: 'POST',
    type: 'POST', // For jQuery < 1.9
    success: function(data){
        alert(data);
    }
});

jQuery'yi sizin için bir Content-Type başlığı eklememeye zorlayarak contentType seçeneğini false olarak ayarlamanız zorunludur, aksi takdirde sınır dizesi eksik olacaktır. Ayrıca, processData bayrağını false olarak ayarlı bırakmalısınız, aksi takdirde jQuery FormDatanızı bir dizeye dönüştürmeye çalışacak ve bu da başarısız olacaktır.

Şimdi PHP kullanarak dosyayı alabilirsiniz:

$_FILES['file-0']

(Dosya girişinizde multiple özelliğini belirtmediyseniz, yalnızca bir dosya vardır, file-0, bu durumda sayılar her dosyayla birlikte artacaktır).

Eski tarayıcılar için FormData emülasyonu kullanılıyor

var opts = {
    url: 'php/upload.php',
    data: data,
    cache: false,
    contentType: false,
    processData: false,
    method: 'POST',
    type: 'POST', // For jQuery < 1.9
    success: function(data){
        alert(data);
    }
};
if(data.fake) {
    // Make sure no text encoding stuff is done by xhr
    opts.xhr = function() { var xhr = jQuery.ajaxSettings.xhr(); xhr.send = xhr.sendAsBinary; return xhr; }
    opts.contentType = "multipart/form-data; boundary="+data.boundary;
    opts.data = data.toString();
}
jQuery.ajax(opts);

Mevcut bir formdan FormData oluşturma

Dosyaları manuel olarak yinelemek yerine, FormData nesnesi mevcut bir form nesnesinin içeriğiyle de oluşturulabilir:

var data = new FormData(jQuery('form')[0]);

Sayaç yerine PHP'ye özgü bir dizi kullanın

Dosya öğelerinizi aynı şekilde adlandırın ve adı parantez içinde bitirin:

jQuery.each(jQuery('#file')[0].files, function(i, file) {
    data.append('file[]', file);
});

$_FILES['file']` daha sonra yüklenen her dosya için dosya yükleme alanlarını içeren bir dizi olacaktır. Aslında bunu benim ilk çözümüme göre tavsiye ederim çünkü yinelemesi daha kolay.

Yorumlar (31)

Sadece Raphael'in harika cevabına bir parça eklemek istedim. Göndermek için JavaScript kullanıp kullanmadığınıza bakılmaksızın PHP'nin aynı `$_FILES'i üretmesini nasıl sağlayacağınız aşağıda açıklanmıştır.

HTML formu:


   <input name="media[]" type="file" multiple/>
   <input class="button" type="submit" alt="Upload" value="Upload" />

PHP, JavaScript olmadan gönderildiğinde bu $_FILES dosyasını üretir:

Array
(
    [media] => Array
        (
            [name] => Array
                (
                    [0] => Galata_Tower.jpg
                    [1] => 518f.jpg
                )

            [type] => Array
                (
                    [0] => image/jpeg
                    [1] => image/jpeg
                )

            [tmp_name] => Array
                (
                    [0] => /tmp/phpIQaOYo
                    [1] => /tmp/phpJQaOYo
                )

            [error] => Array
                (
                    [0] => 0
                    [1] => 0
                )

            [size] => Array
                (
                    [0] => 258004
                    [1] => 127884
                )

        )

)

Dosyaları göndermek için Raphael'in JS'sini kullanarak aşamalı geliştirme yaparsanız...

var data = new FormData($('input[name^="media"]'));       
jQuery.each($('input[name^="media"]')[0].files, function(i, file) {
    data.append(i, file);
});

$.ajax({
    type: ppiFormMethod,
    data: data,
    url: ppiFormActionURL,
    cache: false,
    contentType: false,
    processData: false,
    success: function(data){
        alert(data);
    }
});

... PHP'nin $_FILES dizisi, göndermek için bu JavaScript kullanıldıktan sonra böyle görünür:

Array
(
    [0] => Array
        (
            [name] => Galata_Tower.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpAQaOYo
            [error] => 0
            [size] => 258004
        )

    [1] => Array
        (
            [name] => 518f.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpBQaOYo
            [error] => 0
            [size] => 127884
        )

)

Bu güzel bir dizi ve aslında bazı insanlar $_FILESi dönüştürüyor, ancak ben göndermek için JavaScript kullanılıp kullanılmadığına bakılmaksızın aynı $_FILES ile çalışmanın yararlı olduğunu düşünüyorum. İşte JS'de yapılan bazı küçük değişiklikler:

// match anything not a [ or ]
regexp = /^[^[\]]+/;
var fileInput = $('.putImages input[type="file"]');
var fileInputName = regexp.exec( fileInput.attr('name') );

// make files available
var data = new FormData();
jQuery.each($(fileInput)[0].files, function(i, file) {
    data.append(fileInputName+'['+i+']', file);
});

(14 Nisan 2017 düzenlemesi: FormData() yapıcısından form öğesini kaldırdım - bu Safari'deki bu kodu düzeltti).

Bu kod iki şey yapar.

  1. HTML'yi daha sürdürülebilir hale getirerek input ad niteliğini otomatik olarak alır. Şimdi, form putImages sınıfına sahip olduğu sürece, diğer her şey otomatik olarak halledilir. Yani, inputun herhangi bir özel isme sahip olması gerekmez.
  2. Normal HTML'in gönderdiği dizi biçimi, JavaScript tarafından data.append satırında yeniden oluşturulur. Parantezlere dikkat edin.

Bu değişikliklerle, JavaScript ile gönderme artık basit HTML ile gönderme ile tam olarak aynı $_FILES dizisini üretir.

Yorumlar (1)

Bu işlevi okuduğum bazı bilgilere dayanarak oluşturdum.

Bunu .serialize() kullanır gibi kullanın, bunun yerine sadece .serializefiles(); koyun.
Burada testlerimde çalışıyor.

//USAGE: $("#form").serializefiles();
(function($) {
$.fn.serializefiles = function() {
    var obj = $(this);
    /* ADD FILE TO PARAM AJAX */
    var formData = new FormData();
    $.each($(obj).find("input[type='file']"), function(i, tag) {
        $.each($(tag)[0].files, function(i, file) {
            formData.append(tag.name, file);
        });
    });
    var params = $(obj).serializeArray();
    $.each(params, function (i, val) {
        formData.append(val.name, val.value);
    });
    return formData;
};
})(jQuery);
Yorumlar (2)