Запрос данных 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();
............
...............