2012-02-27 8 views
11

Estoy escribiendo una API RESTful. Tengo problemas para subir imágenes con los diferentes verbos.Solicitud de PUT de datos de formulario multiparte de PHP?

Considere:

Tengo un objeto que puede ser creado/modificado/borrado/visto a través de una entrada/put/delete/get petición a una URL. La solicitud es un formulario de varias partes cuando hay que cargar un archivo, o una aplicación/xml cuando solo hay texto para procesar.

Para manejar las cargas de imágenes que se asocian con el objeto que estoy haciendo algo como:

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'); 
    } 

El principal problema aquí es cuando se trata de gestionar una petición PUT, obviamente, $ _POST De no contiene la opción de venta datos (¡hasta donde puedo decir!).

de referencia, esta es la forma en que estoy construyendo las solicitudes:

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

¿Alguien tiene alguna idea de cómo puedo acceder a la variable xml en mi solicitud PUT?

Respuesta

24

En primer lugar, $_FILES no se llena cuando se manejan las solicitudes PUT. Solo está poblado por PHP cuando se manejan solicitudes POST.

Debe analizarlo manualmente. Eso se aplica a los campos "normales", así:

// 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; 
     } 
    } 

} 

En cada iteración, la matriz $data se rellenará con sus parámetros, y la matriz $headers se rellenará con los encabezados para cada parte (por ejemplo: Content-Type, etc. .) y $filename contendrá el nombre del archivo original, si se incluye en la solicitud y es aplicable al campo.

Tenga en cuenta que lo anterior solo funcionará para los tipos de contenido multipart. Asegúrese de marcar el encabezado de solicitud Content-Type antes de usar lo anterior para analizar el cuerpo.

+0

Gracias, eso es más cargas útiles :) – Josh

+1

"En primer lugar, $ _FILES no se rellena al manejar peticiones PUT. Sólo está poblada por PHP al manejar peticiones POST." No puedo encontrar documentación sobre esto, ¿pueden indicarme la dirección correcta? – WDRust

+1

@ M.Ang .: [Aquí] (http://php.net/manual/en/features.file-upload.post-method.php): "PHP también es compatible con las cargas de archivos del método PUT tal como lo utilizan Netscape Composer y los clientes de Amaya del W3C. Consulte el [Soporte de métodos PUT] (http://php.net/manual/en/features.file-upload.put- method.php) para más detalles ". – netcoder

0

Citando respuesta netcoder: "Tome en cuenta lo anterior sólo funcionará para los tipos de contenido de varias partes"

para trabajar con cualquier tipo de contenido que he añadido las siguientes líneas a la solución del Sr. 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(); 
    ............ 
    ............... 
+0

@Greg No tenía permiso de edición en el momento en que agregué la solución. y si agrego mi código como un comentario en el hilo de netcoder, entonces el código no sería legible. ¿Por qué -1? ¿No intenté ayudar a alguien? – sudip

8

Por favor, no elimine esto de nuevo, ¡es útil para la mayoría de las personas que vienen aquí! Todas las respuestas anteriores fueron respuestas parciales que no cubren la solución ya que la mayoría de las personas que hacen esta pregunta querrían.

Esto toma lo que se ha dicho anteriormente y, además, maneja múltiples cargas de archivos y los coloca en $ _FILES como alguien podría esperar. Para que esto funcione, debe agregar 'Script PUT /put.php' a su host virtual para el proyecto por Documentation. También sospecho que tendré que configurar un cron para limpiar cualquier archivo '.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; 
} 
+0

Parece que esta es la única respuesta que se acerca a una solución completa. No estoy seguro de cómo me siento acerca de falsificar '$ _FILES' y' $ _PUT', pero funciona muy bien. ¡Gracias! –

+0

¡eso es justo lo que necesito! –

+0

¿Cómo puedo poner esto en práctica en una solicitud de Laravel? "Iluminar \ Http \ Solicitud" –

Cuestiones relacionadas