bigcommerce api php library code patch

If you’re using the Bigcommerce API via the PHP library in any professional capacity, here’s an important code update that will save you some frustration.  We recently completed a project that involved using the library in our own high availability web-based PIM (product information management) system, and discovered a set of deficiencies that quickly became miserable showstoppers.  We make many requests per second using the same static Bigcommerce v2 REST library function calls, and it’s under this context we noticed the errors.  At present the GitHub PHP library project hasn’t been updated in about a year (since 2013) so if you’re using this library and experiencing issues with it, read on below.

Our observation after utilizing and working with Bigcommerce support to troubleshoot the API in a staging/preproduction capacity was that the code itself was in need of an important set of patches.

Namely, the biggest issue was that the official version did not properly handle the private CURL member object.  The CURL object referenced inside the big-commerce.php class is being reused in the POST, PUT, DELETE, and GET methods, and, after the use of each method the CURL member object simply was not being reset correctly.  More specifically, before it re-uses the CURL object for another call under the same initialization settings, it needed to reset old state flags.

Another issue we discovered was that API calls to the class involving the underlying PUT request was not properly closing a temporary file that had been opened just a few lines earlier.

The consequences of these bugs when attempting to make concurrent and chained requests inside the class when reusing the object were:

  • Deadlocks: the code would simply hang and block the PHP thread, shutting down our engine.
  • Sporadic ‘Content Not Found’ Errors:  which seemingly happened randomly from time to time.

The ‘sporadic’ errors weren’t so unpredictable after all, they resulted from a very specific series of call sequences to each HTTP type.  For example, a deadlock was ALWAYS reproduce-able when calling a POST method immediately after a PUT method.  If you didn’t call the code in exactly that order using the same static Bigcommerce instance you’d never observe this bug.

Below, we’ve highlighted in blue the patches we made to the Bigcommerce API PHP library class to correct the above errors.  Also if you’d prefer, click here to download the revised PHP class directly (the one we use ourselves in our live professional products).  Please note, as of the time of writing this article, the code updates below have been in production without fault now for roughly 6 weeks and counting (since October 1st, 2014).

Please use at your own risk, the library itself is not authored by Jasper Studios and as such may contain other issues we haven’t yet come across.  Please drop us a line if this has at all been helpful to you, we’d love to hear from you.

//////////////////////////////////////////////////////////////////////////////////
public function get($url, $query = false) {
$this->initializeRequest();
if (is_array($query)) {
$url .= '?' . http_build_query($query);
}
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($this->curl, CURLOPT_URL, $url);
// CODE PATCH TO LIBRARY: MAKE SURE WE RESET THE PUT AND POST SETTINGS TO FALSE
// WHEN REUSING THE SAME CURL INSTANCE
curl_setopt($this->curl, CURLOPT_POST, false);
curl_setopt($this->curl, CURLOPT_PUT, false);
curl_setopt($this->curl, CURLOPT_HTTPGET, true);
curl_exec($this->curl);
return $this->handleResponse();
}
//////////////////////////////////////////////////////////////////////////////////
public function post($url, $body) {
$this->addHeader('Content-Type', $this->getContentType());
if (!is_string($body)) {
$body = json_encode($body);
}
$this->initializeRequest();
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($this->curl, CURLOPT_URL, $url);
curl_setopt($this->curl, CURLOPT_POST, true);
// CODE PATCH TO LIBRARY: MAKE SURE WE RESET THE PUT AND HTTPGET SETTINGS TO FALSE
// WHEN REUSING THE SAME CURL INSTANCE
curl_setopt($this->curl, CURLOPT_PUT, false);
curl_setopt($this->curl, CURLOPT_HTTPGET, false);
curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body);
curl_exec($this->curl);
return $this->handleResponse();
}
//////////////////////////////////////////////////////////////////////////////////
public function put($url, $body) {
$this->addHeader('Content-Type', $this->getContentType());
if (!is_string($body)) {
$body = json_encode($body);
}
$this->initializeRequest();
$handle = tmpfile();
fwrite($handle, $body);
fseek($handle, 0);
curl_setopt($this->curl, CURLOPT_INFILE, $handle);
curl_setopt($this->curl, CURLOPT_INFILESIZE, strlen($body));
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($this->curl, CURLOPT_URL, $url);
// CODE PATCH TO LIBRARY: MAKE SURE WE RESET THE POST AND HTTPGET SETTINGS TO FALSE
// WHEN REUSING THE SAME CURL INSTANCE
curl_setopt($this->curl, CURLOPT_HTTPGET, false);
curl_setopt($this->curl, CURLOPT_POST, false);
curl_setopt($this->curl, CURLOPT_PUT, true);
curl_exec($this->curl);
// THIS NEXT LINE WAS ADDED BY THE SUGGESTION OF BIGCOMMERCE SUPPORT
// CLOSE THE FILE HANDLE AFTER USING IT TO PASS THROUGH TO PUT REQUEST.
// WITHOUT CLOSING THIS, EVENTUALLY PHP WILL CRAP OUT RUNNING OUT OF LOCAL MEMORY
// CAUSING SPORADIC/INTERMITTENT FAILURES OBSERVED PREVIOUSLY
// JON C. MARSELLA – jasperStudios.com – OCTOBER 2014
fclose($handle);
// THIS NEXT LINE WAS ADDED TO RESET THE STATUS OF CURL'S INFILE SO THAT
// IT WON'T THROW A CURLOPT_INFILE 'resource has gone away' ERROR MESSAGE ON THE NEXT CALL ATTEMPT
curl_setopt($this->curl, CURLOPT_INFILE, STDIN);
return $this->handleResponse();
}
//////////////////////////////////////////////////////////////////////////////////
public function delete($url) {
$this->initializeRequest();
// NEXT 3 LINES ADDED BY JON C. MARSELLA - BUG FOUND WHEN THE SAME CURL INSTANCE WAS USED
// TO CALL PUT IMMEDIATELY PRECEDING A CALL TO DELETE.
// CURLOPT_PUT, CURLOPT_HTTPGET AND CURLOPT_POST NEED TO BE RESET TO FALSE PRIOR, OTHERWISE
// CURL WAS DEADLOCKING AND THAT JUST DOWNRIGHT WASN'T VERY GOOD AT ALL.
curl_setopt($this->curl, CURLOPT_PUT, false);
curl_setopt($this->curl, CURLOPT_HTTPGET, false);
curl_setopt($this->curl, CURLOPT_POST, false);
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($this->curl, CURLOPT_URL, $url);
curl_exec($this->curl);
return $this->handleResponse();
}