Nowadays, if you don’t want your applications to look like retardated bastards, you have to make the RESTful. Use GET, POST, DELETE and PUT, use consistent URIs, ask for specific content types, and your app will be RESTful.
While writing a tiny RESTful framework (with Rails-like route) for PHP, I had a serious issue with PUT.
Reading the data of a request sent by the PUT verb is as simple (or as silly) as :
$data = file_get_contents('php://input');
This is okay for a single chunk of anonymous data, but the PUT method is actually very similar to POST, except that the URI should represent an existing resource.
With the POST method, PHP knows how to automatically parse the data in order to create an easy, shiny $_POST[] array. With the PUT method, as nothing differs but the verb, PHP should also fill an array. If this is not the $_PUT[] array, it could at least be the $_POST[] array.
But PHP doesn’t. You have to get the unparsed data, and then you have to reinvent the wheel and painfully extract the different parts. Frankly, it sucks.
Here’s a quick and very dirty way to do the boring job. Far from being perfect, but it mostly does the trick.
static function _handle_put_data() {
if (strcasecmp($_SERVER['REQUEST_METHOD'], 'PUT') !== 0 ||
($content_type = @$_SERVER['CONTENT_TYPE']) === '' ||
@$_SERVER['CONTENT_LENGTH'] <= 0) {
return;
}
$matches = array();
if (preg_match('~^multipart/form-data;\s*boundary=(.+)$~',
$content_type, $matches) <= 0 ||
($boundary = $matches[1]) === '') {
return;
}
$data = file_get_contents("php://input");
if (empty($data)) {
return;
}
$p = 0;
while (($bpos = strpos($data, $boundary . "\r\n", $p)) !== FALSE) {
$p = $bpos + strlen($boundary . "\r\n");
if (preg_match('~^Content-Disposition:\s*form-data;\s*name="(.+)"~', substr($data, $p), $matches) <= 0 ||
($var = $matches[1]) === '') {
break;
}
$p += strlen($matches[0]);
if (($p = strpos($data, "\r\n\r\n", $p)) === FALSE) {
break;
}
$p += strlen("\r\n\r\n");
$eop = strpos($data, $boundary, $p);
if ($eop === FALSE) {
$value = substr($data, $p);
} else {
$value = @substr($data, $p, $eop - $p - 4);
}
if (get_magic_quotes_gpc()) {
$_POST[$var] = addslashes($value);
} else {
$_POST[$var] = $value;
}
$p = $eop;
}
}
On the other hand, sending multipart data with the HTTP PUT method is a breeze with the Curl extension:
function curl_simple_wrapper($method, $url, $data = array()) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, DEFAULT_CURL_TIMEOUT);
curl_setopt($ch, CURLOPT_TIMEOUT, DEFAULT_CURL_TIMEOUT);
switch (strtoupper($method)) {
case 'GET':
break;
case 'POST':
case 'PUT':
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
default:
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
}
$ret = curl_exec($ch);
$status = curl_getinfo($ch);
curl_close($ch);
if ($status['http_code'] < 200 || $status['http_code'] >= 300) {
return FALSE;
}
return $ret;
}
The key here is CURLOPT_CUSTOMREQUEST, that let you specify any verb, including POST and PUT.
Anyway, I really hope that PHP will soon be natively able to decode multipart data sent with the PUT verb, just like it already does with the POST verb.