Запрос данных PAT для множественной формы PHP?

Я пишу RESTful API. У меня возникают проблемы с загрузкой изображений с использованием разных глаголов.

Рассмотрим:

У меня есть объект, который можно создать/изменить/удалить/просмотреть через запрос post/put/delete/get к URL. Запрос представляет собой многочастную форму, когда есть файл для загрузки, или application/xml, когда есть только текст для обработки.

Чтобы обрабатывать загружаемые изображения, связанные с объектом, я делаю что-то вроде:

    if(isset($_FILES['userfile'])) {
        $data = $this->image_model->upload_image();
        if($data['error']){
            $this->response(array('error' => $error['error']));
        }
        $xml_data = (array)simplexml_load_string( urldecode($_POST['xml']) );           
        $object = (array)$xml_data['object'];
    } else {
        $object = $this->body('object');
    }

Основная проблема здесь заключается в попытке обработать запрос put, очевидно, что $_POST не содержит данных put (насколько я могу судить!).

Для справки это то, как я строю запросы:

curl -F [email protected]/image.png -F xml="<xml><object>stuff to edit</object></xml>" 
  http://example.com/object -X PUT

Есть ли у кого-нибудь идеи, как я могу получить доступ к переменной xml в моем запросе PUT?

Ответ 1

Прежде всего, $_FILES не заполняется при обработке запросов PUT. Он заполняется только PHP при обращении с запросами POST.

Вам нужно разобрать его вручную. Это также относится к "регулярным" полям:

// Fetch content and determine boundary
$raw_data = file_get_contents('php://input');
$boundary = substr($raw_data, 0, strpos($raw_data, "\r\n"));

// Fetch each part
$parts = array_slice(explode($boundary, $raw_data), 1);
$data = array();

foreach ($parts as $part) {
    // If this is the last part, break
    if ($part == "--\r\n") break; 

    // Separate content from headers
    $part = ltrim($part, "\r\n");
    list($raw_headers, $body) = explode("\r\n\r\n", $part, 2);

    // Parse the headers list
    $raw_headers = explode("\r\n", $raw_headers);
    $headers = array();
    foreach ($raw_headers as $header) {
        list($name, $value) = explode(':', $header);
        $headers[strtolower($name)] = ltrim($value, ' '); 
    } 

    // Parse the Content-Disposition to get the field name, etc.
    if (isset($headers['content-disposition'])) {
        $filename = null;
        preg_match(
            '/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/', 
            $headers['content-disposition'], 
            $matches
        );
        list(, $type, $name) = $matches;
        isset($matches[4]) and $filename = $matches[4]; 

        // handle your fields here
        switch ($name) {
            // this is a file upload
            case 'userfile':
                 file_put_contents($filename, $body);
                 break;

            // default for all other files is to populate $data
            default: 
                 $data[$name] = substr($body, 0, strlen($body) - 2);
                 break;
        } 
    }

}

На каждой итерации массив $data будет заполнен вашими параметрами, а массив $headers будет заполнен заголовками для каждой части (например: Content-Type и т.д.), а $filename будет содержат исходное имя файла, если оно представлено в запросе и применимо к полю.

Обратите внимание, что приведенное выше будет работать только для типов контента multipart. Обязательно проверьте заголовок запроса Content-Type, прежде чем использовать вышеописанное, чтобы проанализировать тело.

Ответ 2

Пожалуйста, не удаляйте это снова, это полезно большинству людей, приезжающих сюда! Все предыдущие ответы были частичными ответами, которые не охватывают решение, поскольку большинство людей, задающих этот вопрос, захотят.

Это берет то, что было сказано выше, и дополнительно обрабатывает несколько загрузок файлов и помещает их в $_FILES, как и ожидалось. Чтобы это сработало, вам нужно добавить 'Script PUT/put.php' на ваш виртуальный хост для проекта Документация. Я также подозреваю, что мне придется настроить cron для очистки любых файлов .tmp.

private function _parsePut(  )
{
    global $_PUT;

    /* PUT data comes in on the stdin stream */
    $putdata = fopen("php://input", "r");

    /* Open a file for writing */
    // $fp = fopen("myputfile.ext", "w");

    $raw_data = '';

    /* Read the data 1 KB at a time
       and write to the file */
    while ($chunk = fread($putdata, 1024))
        $raw_data .= $chunk;

    /* Close the streams */
    fclose($putdata);

    // Fetch content and determine boundary
    $boundary = substr($raw_data, 0, strpos($raw_data, "\r\n"));

    if(empty($boundary)){
        parse_str($raw_data,$data);
        $GLOBALS[ '_PUT' ] = $data;
        return;
    }

    // Fetch each part
    $parts = array_slice(explode($boundary, $raw_data), 1);
    $data = array();

    foreach ($parts as $part) {
        // If this is the last part, break
        if ($part == "--\r\n") break;

        // Separate content from headers
        $part = ltrim($part, "\r\n");
        list($raw_headers, $body) = explode("\r\n\r\n", $part, 2);

        // Parse the headers list
        $raw_headers = explode("\r\n", $raw_headers);
        $headers = array();
        foreach ($raw_headers as $header) {
            list($name, $value) = explode(':', $header);
            $headers[strtolower($name)] = ltrim($value, ' ');
        }

        // Parse the Content-Disposition to get the field name, etc.
        if (isset($headers['content-disposition'])) {
            $filename = null;
            $tmp_name = null;
            preg_match(
                '/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/',
                $headers['content-disposition'],
                $matches
            );
            list(, $type, $name) = $matches;

            //Parse File
            if( isset($matches[4]) )
            {
                //if labeled the same as previous, skip
                if( isset( $_FILES[ $matches[ 2 ] ] ) )
                {
                    continue;
                }

                //get filename
                $filename = $matches[4];

                //get tmp name
                $filename_parts = pathinfo( $filename );
                $tmp_name = tempnam( ini_get('upload_tmp_dir'), $filename_parts['filename']);

                //populate $_FILES with information, size may be off in multibyte situation
                $_FILES[ $matches[ 2 ] ] = array(
                    'error'=>0,
                    'name'=>$filename,
                    'tmp_name'=>$tmp_name,
                    'size'=>strlen( $body ),
                    'type'=>$value
                );

                //place in temporary directory
                file_put_contents($tmp_name, $body);
            }
            //Parse Field
            else
            {
                $data[$name] = substr($body, 0, strlen($body) - 2);
            }
        }

    }
    $GLOBALS[ '_PUT' ] = $data;
    return;
}

Ответ 3

Указание ответа netcoder: "Обратите внимание, что приведенное выше будет работать только для типов многостраничного контента"

Чтобы работать с любым типом контента, я добавил следующие строки в решение г-на netcoder:

   // Fetch content and determine boundary
   $raw_data = file_get_contents('php://input');
   $boundary = substr($raw_data, 0, strpos($raw_data, "\r\n"));

   /*...... My edit --------- */
    if(empty($boundary)){
        parse_str($raw_data,$data);
        return $data;
    }
   /* ........... My edit ends ......... */
    // Fetch each part
    $parts = array_slice(explode($boundary, $raw_data), 1);
    $data = array();
    ............
    ...............