Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to upload files (multipart/form-data) with multidimensional POSTFIELDS using PHP and CURL?

I'm having problems with posting a multidimensional array with file uploads using PHP and CURL.

The multidimensional array is for example:

$post['question'] = 'Are you human?';
$post['answers'] = array('yes', 'no', 'maybe');
$post['file'] = '@/path/to/file';

// Output:

Array(
    'question' => Are you human?,
    'answers' => Array(
        '0' => yes,
        '1' => no,
        '2' => maybe
        ),
    'file' => @/path/to/file
)

There are a few things why this wouldn't work if you simply try to post this with CURLOPT_POSTFIELDS in CURL like this:

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'http://example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);

$response = curl_exec($ch);

First of all the official PHP description of CURLOPT_POSTFIELDS says:

The full data to post in a HTTP "POST" operation. To post a file, prepend a filename with @ and use the full path. This can either be passed as a urlencoded string like 'para1=val1&para2=val2&...' or as an array with the field name as key and field data as value. If value is an array, the Content-Type header will be set to multipart/form-data.

It sounds like you can pass any kind of array to POSTFIELDS right? Wrong. POSTFIELDS only accepts non-scalar values and will choke with a Array to string conversion error when passing on multi-dimensional arrays. So, the only other option you have is to http_build_query() your array to be able to pass multidimensional arrays that don't choke.

But.. as you can read in the note on the PHP page:

Note: Passing an array to CURLOPT_POSTFIELDS will encode the data as multipart/form-data, while passing a URL-encoded string will encode the data as application/x-www-form-urlencoded.

The post will not be multipart/form-data encoded if you pass an urlencoded string to POSTFIELDS, causing your file upload to fail.

So it seems nearly impossible to combine the two with CURL, while it wouldn't be a problem if you'd used a regular HTML form.

My question is: is it possible to bypass this weird quirk of CURL to be able to post multidimensional arrays and file uploads?

like image 434
Melvin Avatar asked Aug 10 '10 20:08

Melvin


3 Answers

multipart/form-data doesn't support nested values. And I don't believe CURL can do it either.

I suspect the receiving end of your request is also a PHP script. If, then you can submit a nested array as one of the values, if you just prepare it yourself:

 $post['answers[0]'] = "yes";
 $post['answers[1]'] = "no";
 $post['answers[2]'] = "maybe";

Theoretically you'd just need 'answers[]' without the index, but that would overwrite the preceeding value - and thus only works with http_build_query.

I'm not sure if there is any HTTP library in PHP which can do this automatically.

like image 51
mario Avatar answered Oct 21 '22 22:10

mario


Try this recursive function.

https://gist.github.com/yisraeldov/ec29d520062575c204be7ab71d3ecd2f

<?php
function build_post_fields( $data,$existingKeys='',&$returnArray=[]){
    if(($data instanceof CURLFile) or !(is_array($data) or is_object($data))){
        $returnArray[$existingKeys]=$data;
        return $returnArray;
    }
    else{
        foreach ($data as $key => $item) {
            build_post_fields($item,$existingKeys?$existingKeys."[$key]":$key,$returnArray);
        }
        return $returnArray;
    }
}

And you can use it like this.

curl_setopt($ch, CURLOPT_POSTFIELDS, build_post_fields($postfields));
like image 1
Yisrael Dov Avatar answered Oct 21 '22 22:10

Yisrael Dov


Another way to accomplish the first answer:

foreach( array("yes","no","maybe") as $key=>$value )
    $post["answers[$key]"] = $value;
like image 2
mkuech Avatar answered Oct 21 '22 22:10

mkuech