PHP Has A Multiselect Bug And Rails Doesn’t Like It

Tom Phillips
In the weeds
Published in
4 min readMar 21, 2016

As a solutions engineer at Greenhouse, I speak with many of our customers’ engineers. We engineers approach problems in different ways, using different skills. As a result, our customers often have different issues when setting up job boards. Most are easy to diagnose. Is the job board getting cut-off? It’s likely there’s a CSS layer covering it up. Font not carrying over? The source is probably not being sent over HTTPS. API rejecting applications? Probably forgot to base64-encode your API key. So, when one of our customers reported that they were submitting applications to our API, receiving a success response, but never seeing the applications appear in Greenhouse, it seemed likely this was another easily-solved configuration issue.

Turned out, I was wrong.

Full disclosure: I’m a career PHP programmer who had only a passing knowledge of Ruby before coming to Greenhouse. Adapting to the language was a challenge and I still have my moments where I miss access to a good Interface. When a customer was having issues submitting candidates to our API with PHP, I was not expecting to spend days on the problem — primarily because I had solved similar problems before. What I didn’t know was that the trickery I had previously used to get around a particular PHP bug would no longer work.

The PHP Bug in question is found here. In summary, when using PHP’s Curl library, a POST request looks like this:

<?php 
$postVars = array(
'var1' => 'foo',
'var2' => 'bar',
'var3' => 'baz'
);

$ch = curl_init('http://www.example.com');
curl_setopt($ch, CURL_POST, true);
curl_setopt($ch, CURL_POSTFIELDS, $postVars);
curl_exec($ch);

The Curl Library (libcurl) translates this array to POST parameters and sends along the request. It does all these things under the hood, so most of the important elements (like the HTTP multi-part boundary string) can’t be accessed outside the library. Mostly, this isn’t a problem. Except with multi-select form inputs. Some customers may want an application question asking the candidate to select the two programming languages they’re most proficient with from a pre-defined list. The customer then needs a way to communicate to our API both of those values tied to the same question. This presents a problem in PHP as the following parameter array will not work:

$postVars = array(  
'var1' => 'foo',
'var2[]' => 'bar',
'var2[]' => 'baz'
);

When sending the preceding example in to the curl_setopt, only ‘baz’ is sent as a parameter because the second var2[] index overwrites the first. PHP has addressed this problem by allowing the user to set an array index in the hash index:

$postVars = array(  
'var1' => 'foo',
'var2[0]' => 'bar',
'var2[1]' => 'baz'
);

The problem we ran in to at Greenhouse is that only PHP understands this nomenclature. As a Ruby shop, we expect the Rails convention, in which empty brackets [] indicate multiple values on a single key. In other words, the above code block generates a query string that looks like var2[0]=bar&var2[1]=baz while Rails is expecting (and only understands) var2[]=bar&var2[]=baz. This was preventing Greenhouse from processing the data correctly. To increase the complexity, the form was also required to be encoded as multipart/form-data since we had to accept resumes, cover letters, and any other files the customers wanted to submit. Now the POST parameters start to look like this:

$postVars = array(  
'var1' => 'foo',
'var2[0]' => 'bar',
'var2[1]' => 'baz',
'resume' => new \CURLFile(
'/path/to/resume.pdf',
'application/pdf',
'resume'
)
);

Using my slightly out-of-date, pre-CURLFile knowledge of PHP, my workaround plan was to ignore the existence of CURLFile and use the old method of submitting multipart forms, which would have looked something like this:

$postVars = 'var1=foo&' .  
'var2[]=bar&' .
'var3[]=baz&' .
'resume=@/path/to/resume.pdf;' .
'filename=resume;type=application/pdf';

In the PHP 5.4 and earlier world, this would have worked just fine. It’s a bit of a hack, and ignores the superior and cleaner use of CURLFile, but it would have gotten the customer across the finish line. Unfortunately, my out-of-date PHP knowledge was just that — out of date. In the CURLFile world, the @ sign has been deprecated. Without using deprecated code — which may eventually be unsupported code — there’s no way for PHP to submit a multipart form with a multiselect and file upload to a non PHP system with libcurl.

Enter Guzzle. This incredibly useful library gets around libcurl’s limitations by bypassing it entirely and allowing PHP engineers to send multiselects in a way that non-PHP systems will understand. By giving each parameter its own hash, PHP can send the parameter correctly. Using Guzzle, submitting an application via Greenhouse’s API would look like this:

<?php 
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://api.greenhouse.io/'
]);
$headers = array(
'Authorization' => 'Basic ' .
base64_encode($_ENV['YOUR_API_KEY'] .
':');
);
$postVars = array(
['name' => 'first_name', 'contents' => 'Tom'],
['name' => 'last_name', 'contents' => 'P'],
['name' => 'id', 'contents' => '123450'],
['name' => 'email', 'contents' => 'tom@example.com'],
['name' => 'phone', 'contents' => '555-555-5555'],
[
'name' => 'question_0000001',
'contents' => 'test this stuff'
],
['name' => 'question_0000002[]', 'contents' => '0099998'],
['name' => 'question_0000002[]', 'contents' => '0099999'],
[
'name' => 'resume',
'filename' => 'resume',
'contents' => fopen('/path/to/resume.pdf', 'r')
]
);
$response = $client->request(
'POST',
'v1/applications',
array('multipart' => $postVars, 'headers' => $headers)
);

The customer is now able to send the correct names for each part of the multiselect, which sidesteps the PHP bug with a widely-used, composer-friendly library.

Because of the bug in PHP’s libcurl, Greenhouse now recommends Guzzle when using PHP to interact with Greenhouse’s API. This is the best option for engineers until such time PHP addresses their libcurl bug.

Originally published at tech.greenhouse.io on March 21, 2016.

--

--