Задача: из кода на PHP передать веб-службе файл с помощью curl.
Решение стандартное и достаточно простое, но, как обычно, при программировании на PHP есть нюансы. Инициализируем библиотеку curl, формируем данные для POST-запроса, одним из параметров устанавливаем путь к передаваемому файлу, который обязательно начинаем со значка "собаки".
$requestVars = array( 'id' => 1234, 'name' => 'log', 'logfile' => '@/tmp/test.log'); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'test.web.service.net'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $requestVars); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); $res = curl_exec($ch); curl_close($ch);
Смотрим на стороне веб-службы переменные $_POST и $_FILES.
$_POST: Array ( [id] => 1234 [name] => log ) $_FILES: Array ( [logfile] => Array ( [name] => test.log [type] => application/octet-stream [tmp_name] => /tmp/phpfdWZF6 [error] => 0 [size] => 11 ) )
Всё прошло отлично. Curl самостоятельно принял решение об использовании при передаче запроса алгоритма multipart/form-data и передал файл веб-службе. На её стороне файл сохранён с именем /tmp/phpfdWZF6.
Теперь попробуем изменить параметры запроса, добавив туда вложенные массивы.
$requestVars = array( 'id' => array(1, 2, 3, 4), 'name' => 'log', 'logfile' => '@/tmp/test.log');
Файл веб-служба загрузила, а вот в переменной $_POST теперь некорректные данные.
$_POST: Array ( [id] => Array [name] => log )
Проблема в том, что библиотека curl не умеет обрабатывать вложенные массивы, установленные в CURLOPT_POSTFIELDS. Она работает только с одноуровневыми массивами или строкам. Поэтому самое первое и очевидное решение - превратить массив в строку с помощью http_build_query. Пробуем.
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($requestVars, '', '&'));
Параметры в $_POST передались как нужно, но теперь вообще не передался файл.
$_POST: Array ( [id] => Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 ) [name] => log [logfile] => @/tmp/test.log ) $_FILES: Array ( )
Т.к. параметры запроса были переданы строкой, то библиотека curl проявила интеллект и начала использовать алгоритм передачи данных application/x-www-form-urlencoded, который не имеет даже теоретической возможности передачи файла. Продолжаем борьбу. Устанавливаем принудительно в заголовках запроса нужный нам тип контента.
$headers[] = "Content-type: multipart/form-data"; curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
После этого у нас вообще перестаёт работать отправка запроса. Ни параметры запроса, ни файл веб-служба не получает. А всё потому, что начиная с PHP версии 5.2.0 при передаче файлов с префиксом "@" значение в CURLOPT_POSTFIELDS обязательно должно быть массивом. С одной стороны, чтобы передать многоуровневый массив, нам нужна строка. С другой стороны, для передачи файла нужен массив.
Решение, как обычно, посередине. Строим одноуровневый массив с такими ключами, какими они были бы при формировании строки. Т.е. приводим наши параметры к такому виду.
$requestVars = array( 'id[0]' => 1, 'id[1]' => 2, 'id[3]' => 3, 'id[4]' => 4, 'name' => 'log', 'logfile' => '@/tmp/test.log'); curl_setopt($ch, CURLOPT_POSTFIELDS, $requestVars);
И вот теперь мы, наконец-то, получили то, что хотели.
$_POST: Array ( [id] => Array ( [0] => 1 [1] => 2 [3] => 3 [4] => 4 ) [name] => log ) $_FILES: Array ( [logfile] => Array ( [name] => test.log [type] => application/octet-stream [tmp_name] => /tmp/phpfdWZF6 [error] => 0 [size] => 11 ) )
Осталось написать универсальное решение, которое бы преобразовывало любые многоуровневые массивы в одноуровневые. Тут на помощь приходит простенькая рекурсия.
function convertToStringArray( $inputKey, $inputArray, &$resultArray) { foreach ($inputArray as $key => $value) { $tmpKey = (bool)$inputKey ? $inputKey."[$key]" : $key; if (is_array($value)) { convertToStringArray($tmpKey, $value, $resultArray); } else { $resultArray[$tmpKey] = $value; } } }
Тестируем последний раз.
$requestVars = array( 'id' => array(1, 2, 3, 4), 'name' => 'log', 'logfile' => '@/tmp/test.log'); $resultArray = array(); convertToStringArray('', $requestVars, $resultArray); $requestVars = $resultArray; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'test.web.service.net'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $requestVars); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); $res = curl_exec($ch); curl_close($ch);
Убеждаемся, что всё работает, и отправляемся на поиски новых открытий в прекрасном и удивительном мире программирования на PHP.
Комментариев нет:
Отправить комментарий