This took me a day or two.
When making a PUT request with the php curl extension to an API built on the (excellent) Slim framework, you will have to sent an additional header with Content-Type set to application/x-www-form-urlencoded.

My completed method looks like:
(complete class will be shared on request).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
protected function setCurlOpts(array $options)
{
	// default options are set here.
	$defaultOptions = array(
		CURLOPT_TIMEOUT => 10,
		CURLOPT_CONNECTTIMEOUT => 10, // timeout on connect
		CURLOPT_URL => $this->_url,
		CURLOPT_RETURNTRANSFER => true,
	);
	// load additional options, defaultOptions with the same key will be overwritten by options
	// provided as parameter
	// !! IMPORTANT
	// avoid array_merge here, the curl constants are mappings to integers, array merge
	// will renumber the keys starting from 0.
	// instead we use union (+) for merging the arrays
	$curlOptions = $defaultOptions + $options;
	if (!curl_setopt_array($this->curlHandle, $curlOptions)) {
		throw new restrequestException("failed to set cURL options");
	}
	return true;
}
protected function executePut()
{
	$this->requestLength = strlen($this->requestBody);
	$stream = fopen('php://memory', 'r+');
	fwrite($stream, $this->requestBody);
	rewind($stream);
	$options = array(CURLOPT_HTTPHEADER => array(
	    'Content-Type: application/x-www-form-urlencoded',
	    'Accept: ' . $this->_acceptType
	    ),
		CURLOPT_INFILE => $stream,
		CURLOPT_INFILESIZE => $this->requestLength,
		CURLOPT_PUT => true,
		);
	$this->setCurlOpts($options);
	$this->doExecute($this->curlHandle);
	@fclose($stream);
}