Смешивание нескольких значений для одного и того же ключа и загрузки файлов с использованием cURL и PHP

Ive сталкиваются с ограничением в привязках cURL для PHP. Похоже, нет простого способа отправить те же самые значения для одного и того же ключа для postfields. В большинстве обходных решений, с которыми я столкнулся, это связано с созданием полей почтового кодирования почтовых полей вручную тегом = foo & tag = bar & tag = baz) вместо использования версии ассоциативного массива CURLOPT_POSTFIELDS.

Кажется, что довольно часто нужно поддерживать, поэтому я чувствую, что, должно быть, что-то пропустил. Является ли это единственным способом обработки нескольких значений для одного и того же ключа?

Хотя это обходное решение можно считать работоспособным (если не очень раздражать), моя основная проблема заключается в том, что мне нужно иметь возможность делать несколько значений для одного и того же ключа, а также поддерживать загрузку файлов. Насколько я могу судить, для загрузки файлов более или менее требуется использовать ассоциированную версию arravy CURLOPT_POSTFIELDS. Поэтому я чувствую, что застрял.

У меня опубликовано об этой проблеме более подробно в списке рассылки cURL PHP в надежде, что у кого-то есть некоторые идеи об этом.

Предложения или подсказки о том, где я могу найти дополнительную информацию по этому поводу, очень ценятся!

Ответ 1

В итоге я написал свою собственную функцию для создания пользовательской строки CURLOPT_POSTFIELDS с данными multipart/form. Какая боль.

function curl_setopt_custom_postfields($ch, $postfields, $headers = null) {
    // $postfields is an assoc array.
    // Creates a boundary.
    // Reads each postfields, detects which are @files, and which values are arrays
    // and dumps them into a new array (not an assoc array) so each key can exist
    // multiple times.
    // Sets content-length, content-type and sets CURLOPT_POSTFIELDS with the
    // generated body.
}

Я смог использовать этот метод следующим образом:

curl_setopt_custom_postfields($ch, array(
    'file' => '@/path/to/file',
    'tag' => array('a', 'b', 'c'),
));

Я не уверен в стеках CURLOPT_HTTPHEADER, поэтому, поскольку этот метод вызывает его, я убедился, что функция позволит пользователю указывать дополнительные заголовки, если это необходимо.

У меня есть полный код, доступный в этом сообщении в блоге.

Ответ 2

Если вы используете tag[] вместо tag для имени, PHP будет генерировать массив для вас, другими словами, а не

tag=foo&tag=bar&tag=baz

Вам нужно

tag[]=foo&tag[]=bar&tag[]=baz

Обратите внимание, что когда urlencoded для передачи это должно стать

tag%5B%5D=foo&tag%5B%5D=bar&tag%5B%5D=baz

Ответ 3

Я работал, используя:

curl_setopt($ch, CURLOPT_POSTFIELDS,array('tag[0]'=>'val0','tag[1]'=>'val1'));

то $_POST приводит к: $_POST['tag'][0] = 'val0' и $_POST['tag'][1] = 'val1'

Ответ 4

Я столкнулся с той же проблемой. Но я смог решить это так.

for($cnt = 0; $cnt < count($siteRows); $cnt++)
{
    $curlParams['site_ids['.$cnt.']'] = $siteRows[$cnt]->site_id; 
}

Работает также с файлами:

for($cnt = 0; $cnt < count($imageRows); $cnt++)
{
    $curlParams['product_images['.$cnt.']'] = '@'.$imageRows[$cnt]->full_path;
}

Ответ 5

Я думаю, что установленный стандарт для нескольких значений в одном ключе (или тот же ключ) состоит в объединении с разделителем, например, для множественного выбора списков опций в элементах формы. Я считаю, что этот разделитель является символом табуляции (\t) или символом трубы (|).

Если имя ключа завершено с помощью [] (например, tag[]), PHP автоматически преобразует значения в массив для вашего удобства.

Ответ 6

lImbus и paul, спасибо за ваш вклад.

Если бы я контролировал форму, которую я отправляю, я мог бы найти альтернативное решение этой проблемы. Однако я не контролирую форму. И я почти уверен, что программное обеспечение, читающее сообщение, не является PHP и не подчиняется стандарту tag [].

Даже если это так, cURL, похоже, не подчиняется синтаксису tag []. В принципе, я пробовал следующее и не работал...

curl_setopt($ch, CURLOPT_POSTFIELDS, array('file' => '@/pathtofile', 'tag[]' => array('a', 'b', 'c'));

curl_setopt($ch, CURLOPT_POSTFIELDS, array('file' => '@/pathtofile', 'tag' => array('a', 'b', 'c'));

И снова я не думаю, что передача тега [] будет работать в любом случае, поскольку форма, которую я отправляю, фактически ищет "тег", а не "тег []".

У меня действительно возникает ощущение, что привязки cURL PHP действительно не поддерживают этого. Что мне кажется таким удивительным. Похоже, что он может делать буквально все остальное, но он не может сделать что-то простое, как это?

Ответ 7

  1. Проголосуйте за PHP Bug # 51634.
  2. Попробуйте ответ @BeauSimensen.
  3. Жрет может это сделать. Смотрите пример ниже.
$client = new \GuzzleHttp\Client();
$client->request('POST', $url, [
  'multipart' => [
    [ 'name' => 'foo', 'contents' => 'bar' ],
    [ 'name' => 'foo', 'contents' => 'baz' ],
  ]
]);

Ответ 8

Я нашел этот ответ онлайн и хочу опубликовать его здесь, прежде чем он исчезнет:

http://yeehuichan.wordpress.com/2011/08/07/sending-multiple-values-with-the-same-namekey-in-curl-post/

function curl_setopt_custom_postfields($ch, $postfields, $headers = null) {
    $algos = hash_algos();
    $hashAlgo = null;
    foreach ( array('sha1', 'md5') as $preferred ) {
        if ( in_array($preferred, $algos) ) {
            $hashAlgo = $preferred;
            break;
        }
    }
    if ( $hashAlgo === null ) { list($hashAlgo) = $algos; }
    $boundary =
        '----------------------------' .
        substr(hash($hashAlgo, 'cURL-php-multiple-value-same-key-support' . microtime()), 0, 12);

    $body = array();
    $crlf = "\r\n";
    $fields = array();
    foreach ( $postfields as $key => $value ) {
        if ( is_array($value) ) {
            foreach ( $value as $v ) {
                $fields[] = array($key, $v);
            }
        } else {
            $fields[] = array($key, $value);
        }
    }
    foreach ( $fields as $field ) {
        list($key, $value) = $field;
        if ( strpos($value, '@') === 0 ) {
            preg_match('/^@(.*?)$/', $value, $matches);
            list($dummy, $filename) = $matches;
            $body[] = '--' . $boundary;
            $body[] = 'Content-Disposition: form-data; name="' . $key . '"; filename="' . basename($filename) . '"';
            $body[] = 'Content-Type: application/octet-stream';
            $body[] = '';
            $body[] = file_get_contents($filename);
        } else {
            $body[] = '--' . $boundary;
            $body[] = 'Content-Disposition: form-data; name="' . $key . '"';
            $body[] = '';
            $body[] = $value;
        }
    }
    $body[] = '--' . $boundary . '--';
    $body[] = '';
    $contentType = 'multipart/form-data; boundary=' . $boundary;
    $content = join($crlf, $body);
    $contentLength = strlen($content);

    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        'Content-Length: ' . $contentLength,
        'Expect: 100-continue',
        'Content-Type: ' . $contentType,
    ));

    curl_setopt($ch, CURLOPT_POSTFIELDS, $content);

}

И использовать его:

curl_setopt_custom_postfields($ch, array(
    'file' => '@a.csv',
    'name' => array('James', 'Peter', 'Richard'),
));