2012-02-27 13 views
11

Sto scrivendo un'API RESTful. Ho problemi con il caricamento di immagini usando i diversi verbi.Dati modulo multipart PHP Richiesta PUT?

Considerate:

Ho un oggetto che può essere creato/modificato/cancellato/consultati tramite un post/put/cancellare/ottenere richiesta a un URL. La richiesta è in più parti quando c'è un file da caricare, o application/xml quando c'è solo un testo da elaborare.

per gestire il caricamento delle immagini che sono associati con l'oggetto che sto facendo qualcosa di simile:

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

Il problema principale qui è quando si cerca di gestire una richiesta put, ovviamente $ _POST non contiene il put dati (per quanto posso dire!).

Per riferimento questo è come mi sto costruendo le richieste:

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

Qualcuno ha qualche idea come posso accedere alla variabile xml nella mia richiesta PUT?

risposta

24

Prima di tutto, $_FILES non viene popolato quando si gestiscono richieste PUT. È popolato da PHP solo quando gestisce le richieste POST.

È necessario analizzarlo manualmente. Questo vale anche per i campi "regolari" così:

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

} 

Ad ogni iterazione, la matrice $data verrà popolata con i parametri, e la matrice $headers verrà popolata con le intestazioni per ogni parte (ad esempio: Content-Type, ecc .), e $filename conterrà il nome file originale, se fornito nella richiesta ed è applicabile al campo.

Prendere nota di quanto sopra funzionerà solo per i tipi di contenuto multipart. Assicurati di controllare l'intestazione della richiesta Content-Type prima di utilizzare il precedente per analizzare il corpo.

+0

Grazie, questo è più carichi utili :) – Josh

+1

"Prima di tutto, $ _FILES non viene popolato durante la manipolazione richieste PUT. E 'popolato solo da PHP durante la manipolazione richieste POST." Non riesci a trovare la documentazione su questo, puoi indicarmi la giusta direzione? – WDRust

+1

@ M.Ang .: [Qui] (http://php.net/manual/en/features.file-upload.post-method.php): "PHP supporta anche i caricamenti di file PUT-metodo utilizzati da Netscape Composer e dai client Amaya di W3C. Consulta il [Supporto metodo PUT] (http://php.net/manual/en/features.file-upload.put- method.php) per maggiori dettagli. " – netcoder

0

Citando risposta netcoder: "Prendere atto quanto sopra funzionerà solo per i tipi di contenuto a più"

per funzionare con qualsiasi tipo di contenuto che ho aggiunto le seguenti righe alla soluzione del signor 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 Non ho avuto il permesso di modifica al momento ho aggiunto la soluzione. e se aggiungo il mio codice come commento nella discussione di netcoder, allora il codice non sarebbe leggibile. Perché -1? Non ho provato ad aiutare qualcuno? – sudip

8

Per favore non cancellarlo di nuovo, è utile per la maggior parte delle persone che vengono qui! Tutte le risposte precedenti sono state risposte parziali che non coprono la soluzione in quanto la maggioranza delle persone che chiede questa domanda vorrebbe.

Questo prende ciò che è stato detto sopra e in aggiunta gestisce più caricamenti di file e li colloca in $ _FILES come qualcuno si aspetterebbe. Per farlo funzionare, devi aggiungere 'Script PUT /put.php' al tuo host virtuale per il progetto per Documentation. Sospetto anche che dovrò impostare un cron per ripulire i file ".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

Sembra davvero che questa sia l'unica risposta che va vicino a una soluzione completa. Non sono sicuro di come mi sento di fingere '$ _FILES' e' $ _PUT' ma funziona molto bene. Grazie! –

+0

è proprio quello di cui ho bisogno! –

+0

Come posso farlo funzionare in una richiesta Laravel? "Illuminare \ Http \ Request" –

Problemi correlati