Posting multidimensional array with PHP and CURL

PhpArraysPostCurl

Php Problem Overview


I'm having trouble posting form data via CURL to a receiving PHP script located on a different host.

I get an Array to string conversion error

This is print_r of the array I'm posting:

Array
(
	[name] => Array
	(
		[0] => Jason
		[1] => Mary
		[2] => Lucy
	)
	[id] => 12
	[status] => local
	[file] => @/test.txt
)

This is the line the error occurs on:

curl_setopt($this->ch, CURLOPT_POSTFIELDS, $post);

The third argument must be an array because I need the Content-Type header to be set to multipart/form-data as I am sending a file via this same array, therefore I cannot convert the array to a query string or use http_build_query().

Also I do not have access to the code on the receiving host so I cannot serialize and unserialize the array.

I'm assuming that the value of the name key being an array is the cause for this error, I'm also assuming that CURLOPT_POSTFIELDS doesn't support multidimensional arrays. Is there any other way around this or am I doomed?

Thanks in advance!

Php Solutions


Solution 1 - Php

function http_build_query_for_curl( $arrays, &$new = array(), $prefix = null ) {

	if ( is_object( $arrays ) ) {
		$arrays = get_object_vars( $arrays );
	}

	foreach ( $arrays AS $key => $value ) {
		$k = isset( $prefix ) ? $prefix . '[' . $key . ']' : $key;
		if ( is_array( $value ) OR is_object( $value )  ) {
			http_build_query_for_curl( $value, $new, $k );
		} else {
			$new[$k] = $value;
		}
	}
}

$arrays = array(
	'name' => array(
		'first' => array(
			'Natali', 'Yura'
		)
	)
);


http_build_query_for_curl( $arrays, $post );

print_r($post);

Solution 2 - Php

The concept of an array doesn't really exist when it comes to HTTP requests. PHP (and likely other server-side languages) has logic baked in that can take request data that looks like an array (to it) and puts it together as an array while populating $_GET, $_POST etc.

For instance, when you POST an array from a form, the form elements often look something like this:

<form ...>
  <input name="my_array[0]">
  <input name="my_array[1]">
  <input name="my_array[2]">
</form>

or even:

<form ...>
  <input name="my_array[]">
  <input name="my_array[]">
  <input name="my_array[]">
</form>

While PHP knows what to do with this data when it receives it (ie. build an array), to HTML and HTTP, you have three unrelated inputs that just happen to have similar (or the same, although this isn't technically valid HTML) names.

To do the reverse for your cURL request, you need to decompose your array into string representations of the keys. So with your name array, you could do something like:

foreach ($post['name'] as $id => $name)
{
  $post['name[' . $id . ']'] = $name;
}
unset($post['name']);

Which would result in your $post array looking like:

Array
(
    [name[0]] => Jason
    [name[1]] => Mary
    [name[2]] => Lucy
    [id] => 12
    [status] => local
    [file] => @/test.txt
)

And then each key in the array you are posting would be a scalar value, which cURL is expecting, and the array would be represented as you need to for HTTP.

Solution 3 - Php

You'd have to build the POST string manually, rather than passing the entire array in. You can then override curl's auto-chose content header with:

curl_setopt($c, CURLOPT_HTTPHEADER, array("Content-type: multipart/form-data"));

Serializing/json-ifying would be easier, but as you say, you have no control over the receiving end, so you've got a bit of extra work to do.

Solution 4 - Php

Simplest solution is to do a :

$array = urldecode(http_build_query($array));

Below is sample code where this is used in real life :

https://gist.github.com/gayanhewa/142c48162f72e68a4a23

When you have nested $params section in the above gist it will parse it accordingly and prepare it for posting via curl.

Solution 5 - Php

First I would like to thank Daniel Vandersluis for his insightful reply. Based on his input I came up with this to fix the problem from the original question:

<?php

function curl_postfields_flatten($data, $prefix = '') {
  if (!is_array($data)) {
    return $data; // in case someone sends an url-encoded string by mistake
  }

  $output = array();
  foreach($data as $key => $value) {
    $final_key = $prefix ? "{$prefix}[{$key}]" : $key;
    if (is_array($value)) {
      // @todo: handle name collision here if needed
      $output += curl_postfields_flatten($value, $final_key);
    }
    else {
      $output[$final_key] = $value;
    }
  }
  return $output;
}

Usage should look like this:

curl_setopt($this->ch, CURLOPT_POSTFIELDS, curl_postfields_flatten($post));

This function will convert arrays like this:

array(
  'a' => 'a',
  'b' => array(
    'c' => array(
      'd' => 'd',
      'e' => array(
        'f' => 'f',
      ),
    ),
  ),
);

Into this:

array(
  'a' => 'a',
  'b[c][d]' => 'd',
  'b[c][e][f]' => 'f',
)

It doesn't handle cases with mixed format when there is a key collision like this:

array(
 'b[c]' => '1',
 'b' => array(
   'c' => '2', 
  ),
);

The output will contain only the first value for that key

array(
 'b[c]' => '1'
)

Solution 6 - Php

The cURL option CURLOPT_POSTFIELDS will accept either a string or simple array but not a nested array. Attempting to do so will generate the Array to string conversion error.

However http_build_query() can handle a nested array so use it to convert the $_POST array to a string then send that string instead. So where you have;

curl_setopt($ch, CURLOPT_POSTFIELDS, $_POST);

use this instead;

curl_setopt($ch, CURLOPT_POSTFIELDS, urldecode(http_build_query($_POST)));

Solution 7 - Php

I think you'll need to pass the options as a string:

curl_setopt($this->ch, CURLOPT_POSTFIELDS, 'name[]=Jason&name[]=Mary&name[]=Lucy...');

You should then be able to set the header manually via CURLOPT_HTTPHEADER.

Solution 8 - Php

$post = "ac=on&p=1&pr[]=0&pr[]=1&a[]=3&a[]=4&pl=on&sp[]=3&ct[]=3&s=1&o=0&pp=3&sortBy=date";
parse_str($post,$fields); 

$url = 'http://example.com/';


//open connection
$ch = curl_init();

//set the url, number of POST vars, POST data
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_POST, true);
curl_setopt($ch,CURLOPT_POSTFIELDS, $fields);
curl_setopt($ch,CURLOPT_RETURNTRANSFER, true);

//execute post
$result = curl_exec($ch);

//close connection
curl_close($ch);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDavid HancockView Question on Stackoverflow
Solution 1 - PhpKhristenko YuraView Answer on Stackoverflow
Solution 2 - PhpDaniel VandersluisView Answer on Stackoverflow
Solution 3 - PhpMarc BView Answer on Stackoverflow
Solution 4 - PhpGayan HewaView Answer on Stackoverflow
Solution 5 - PhpLuxianView Answer on Stackoverflow
Solution 6 - PhpNigel AldertonView Answer on Stackoverflow
Solution 7 - PhpAlex HowanskyView Answer on Stackoverflow
Solution 8 - PhpFarzadView Answer on Stackoverflow