Communicating with a remote API, implemented as a Services 3.0 REST Resource
The following demonstrates how to communicate with a remote API implemented as a Services 3.0 REST resource using standard Drupal authentication (login/logout, user's account roles define user's permissions.) In a RESTful API, there is at least one "resource" defined, where each "resource" is something able to support operations in the CRUD pattern: Create, Retrieve, Update, Delete and Index (get a listing). Typically, a RESTful API implements a series of resources, each of which can have instances created, retrieved, updated and deleted. Aside from the necessity of logging in for authentication, most of what one can do within a RESTful API can be handled through one of five routines: Create(), Get(), Update(), Delete() and Index(). The code presented is a PHP object definition for managing communications with a remote API, primarily handled through routines supporting CRUD operations.
There are two example PHP objects defined here, each supporting the same logic, but for different environments. The first example demonstrates how to communicate with a remote API outside of Drupal (without the Drupal API); this is useful for remote API users who are not Drupal based. The second example demonstrates how to communicate with a remote API using Drupal's API. The Drupal API version uses fewer resources, which may matter to some people. And finally, some sample usage of the remote API communication objects is given. The logic is the same, regardless of using the non-Drupal version or the Drupal version.
Using CURL to communicate with a RESTful API
<?php
// *****************************************************************************************
// defines an object for working with a remote API, not using Drupal API
class RemoteAPI {
public $gateway;
public $apiVersion;
public $status;
public $session; // the session name (obtained at login)
public $sessid; // the session id (obtained at login)
const RemoteAPI_status_unconnected = 0;
const RemoteAPI_status_loggedin = 1;
// *****************************************************************************
public function __construct( $gateway, $apiVersion ) {
$this->gateway = $gateway;
$this->apiVersion = $apiVersion;
$this->status = RemoteAPI_status_unconnected;
$this->session = '';
$this->sessid = '';
}
// *****************************************************************************
// after login, the string generated here needs to be included in any http headers,
// under the key 'Cookie':
private function GetCookieHeader() {
return $this->session.'='.$this->sessid;
}
// *****************************************************************************
// return the standard set of curl options for a POST
private function GetCurlPostOptions( $url, $data, $includeAuthCookie = false ) {
$ret = array( CURLOPT_URL => $url,
CURLOPT_FAILONERROR => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 4,
CURLOPT_HTTPHEADER => array('Accept: application/json'),
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $data,
);
if ($includeAuthCookie) {
$ret[CURLOPT_COOKIE] = $this->GetCookieHeader();
}
return $ret;
}
// *****************************************************************************
// return the standard set of curl options for a GET
private function GetCurlGetOptions( $url, $includeAuthCookie = false ) {
$ret = array( CURLOPT_URL => $url,
CURLOPT_FAILONERROR => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_BINARYTRANSFER => 1,
CURLOPT_TIMEOUT => 3,
CURLOPT_HTTPHEADER => array('Accept: application/json'),
);
if ($includeAuthCookie) {
$ret[CURLOPT_COOKIE] = $this->GetCookieHeader();
}
return $ret;
}
// *****************************************************************************
// return the standard set of curl options for a PUT
private function GetCurlPutOptions( $url, $data, $includeAuthCookie = false ) {
$ret = array( CURLOPT_URL => $url,
CURLOPT_FAILONERROR => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => array('Content-Length: ' . strlen($data),
'Accept: application/json'),
CURLOPT_POSTFIELDS => $data,
);
if ($includeAuthCookie) {
$ret[CURLOPT_COOKIE] = $this->GetCookieHeader();
}
return $ret;
}
// *****************************************************************************
// return the standard set of curl options for a DELETE
private function GetCurlDeleteOptions( $url, $includeAuthCookie = false ) {
$ret = array( CURLOPT_URL => $url,
CURLOPT_FAILONERROR => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_HTTPHEADER => array('Accept: application/json'),
CURLOPT_CUSTOMREQUEST => 'DELETE',
);
if ($includeAuthCookie) {
$ret[CURLOPT_COOKIE] = $this->GetCookieHeader();
}
return $ret;
}
// *****************************************************************************
// return false if we're logged in
private function VerifyUnconnected( $caller ) {
if ($this->status != RemoteAPI_status_unconnected) {
return false;
}
return true;
}
// *****************************************************************************
// return false if we're not logged in
private function VerifyLoggedIn( $caller ) {
if ($this->status != RemoteAPI_status_loggedin) {
return false;
}
return true;
}
// *****************************************************************************
// replace these 'resourceTypes' with the names of your resourceTypes
private function VerifyValidResourceType( $resourceType ) {
switch ($resourceType) {
case 'project':
case 'image':
case 'thingy':
return true;
default: return false;
}
}
// *****************************************************************************
// Perform the common logic for performing an HTTP request with cURL
// return an object with 'response', 'error' and 'info' fields.
private function CurlHttpRequest( $caller, $url, $method, $data, $includeAuthCookie = false ) {
$ch = curl_init(); // create curl resource
switch ($method) {
case 'POST': curl_setopt_array($ch, $this->GetCurlPostOptions($url,$data, $includeAuthCookie)); break;
case 'GET': curl_setopt_array($ch, $this->GetCurlGetOptions($url, $includeAuthCookie)); break;
case 'PUT': curl_setopt_array($ch, $this->GetCurlPutOptions($url, $data, $includeAuthCookie)); break;
case 'DELETE': curl_setopt_array($ch, $this->GetCurlDeleteOptions($url, $includeAuthCookie)); break;
default:
return NULL;
}
$ret = new stdClass;
$ret->response = curl_exec($ch); // execute and get response
$ret->error = curl_error($ch);
$ret->info = curl_getinfo($ch);
curl_close($ch);
if ($ret->info['http_code'] == 200) {
$ret->response = json_decode($ret->response);
}
return $ret;
}
// *****************************************************************************
// Login: uses the cURL library to handle login
public function Login( $username, $password ) {
$callerId = 'RemoteAPI->Login';
if (!$this->VerifyUnconnected( $callerId )) {
return NULL; // error
}
$url = $this->gateway.$this->apiVersion.'/user/login';
$data = array( 'username' => $username, 'password' => $password, );
$data = http_build_query($data, '', '&');
$ret = $this->CurlHttpRequest($callerId, $url, 'POST', $data, false);
if ($ret->info['http_code'] != 200) {
return NULL;
}
else {
$this->sessid = $ret->response->sessid;
$this->session = $ret->response->session_name;
$this->status = RemoteAPI_status_loggedin;
return true; // success!
}
} // end of Login() definition
// *****************************************************************************
// Logout: uses the cURL library to handle logout
public function Logout() {
$callerId = 'RemoteAPI->Logout';
if (!$this->VerifyLoggedIn( $callerId )) {
return NULL; // error
}
$url = $this->gateway.$this->apiVersion.'/user/logout';
$ret = $this->CurlHttpRequest($callerId, $url, 'POST', NULL, true);
if ($ret->info['http_code'] != 200) {
return NULL;
}
else {
$this->status = RemoteAPI_status_unconnected;
$this->sessid = '';
$this->session = '';
return true; // success!
}
} // end of Login() definition
// **************************************************************************
// perform an 'Index' operation on a resource type using cURL.
// Return an array of resource descriptions, or NULL if an error occurs
public function Index( $resourceType ) {
$callerId = 'RemoteAPI->Index';
if (!$this->VerifyLoggedIn( $callerId )) {
return NULL; // error
}
$url = $this->gateway.$this->apiVersion.'/'.$resourceType;
$ret = $this->CurlHttpRequest($callerId, $url, 'GET', NULL, true);
return $ret->response;
}
// *****************************************************************************
// create a new resource of the named type given an array of data, using cURL
public function Create( $resourceType, $resourceData ) {
$callerId = 'RemoteAPI->Create: "'.$resourceType;
if (!$this->VerifyLoggedIn( $callerId )) {
return NULL; // error
}
if (!$this->VerifyValidResourceType($resourceType)) {
return NULL;
}
$url = $this->gateway.$this->apiVersion.'/'.$resourceType;
$data = http_build_query($resourceData, '', '&');
$ret = $this->CurlHttpRequest($callerId, $url, 'POST', $data, true);
return $ret->response;
}
// **************************************************************************
// perform a 'GET' operation on the named resource type and id using cURL.
public function Get( $resourceType, $resourceId ) {
$callerId = 'RemoteAPI->Get: "'.$resourceType.'/'.$resourceId.'"';
if (!$this->VerifyLoggedIn( $callerId )) {
return NULL; // error
}
if (!$this->VerifyValidResourceType($resourceType)) {
return NULL;
}
$url = $this->gateway.$this->apiVersion.'/'.$resourceType.'/'.$resourceId;
$ret = $this->CurlHttpRequest($callerId, $url, 'GET', NULL, true);
return $ret->response;
}
// *****************************************************************************
// update a resource given the resource type and updating array, using cURL.
public function Update( $resourceType, $resourceData ) {
$callerId = 'RemoteAPI->Update: "'.$resourceType;
if (!$this->VerifyLoggedIn( $callerId )) {
return NULL; // error
}
if (!$this->VerifyValidResourceType($resourceType)) {
return NULL;
}
if (!isset($resourceData['data']['id'])) {
return NULL;
}
$url = $this->gateway.$this->apiVersion.'/'.$resourceType.'/'.$resourceData['data']['id'];
$data = http_build_query($resourceData, '', '&');
$ret = $this->CurlHttpRequest($callerId, $url, 'PUT', $data, true);
return $ret->response;
}
// *****************************************************************************
// perform a 'DELETE' operation on the named resource type and id using cURL
public function Delete( $resourceType, $resourceId ) {
$callerId = 'RemoteAPI->Delete: "'.$resourceType;
if (!$this->VerifyLoggedIn( $callerId )) {
return NULL; // error
}
if (!$this->VerifyValidResourceType($resourceType)) {
return NULL;
}
$url = $this->gateway.$this->apiVersion.'/'.$resourceType.'/'.$resourceId;
$ret = $this->CurlHttpRequest($callerId, $url, 'DELETE', NULL, true);
return $ret->response;
}
} // end of RemoteAPI object definition using cURL and not Drupal API
?>
Using the Drupal API to communicate with a RESTful API
<?php
// *****************************************************************************************
// defines an object for working with the remote API, using the Drupal API
class RemoteAPI_via_DrupalAPI {
public $gateway;
public $apiVersion;
public $status;
public $session; // the session name (obtained at login)
public $sessid; // the session id (obtained at login)
const RemoteAPI_status_unconnected = 0;
const RemoteAPI_status_loggedin = 1;
// *****************************************************************************
public function __construct( $gateway, $apiVersion ) {
$this->gateway = $gateway;
$this->apiVersion = $apiVersion;
$this->status = RemoteAPI_status_unconnected;
$this->session = '';
$this->sessid = '';
}
// *****************************************************************************
// after login, the string generated here needs to be included in any http headers,
// under the key 'Cookie':
private function GetCookieHeader() {
return $this->session.'='.$this->sessid;
}
// *****************************************************************************
// return false if we're logged in
private function VerifyUnconnected( $caller ) {
if ($this->status != RemoteAPI_status_unconnected) {
return false;
}
return true;
}
// *****************************************************************************
// return false if we're not logged in
private function VerifyLoggedIn( $caller ) {
if ($this->status != RemoteAPI_status_loggedin) {
return false;
}
return true;
}
// *****************************************************************************
// replace these with the resource type names you'll be using
private function VerifyValidResourceType( $resourceType ) {
switch ($resourceType) {
case 'project':
case 'image':
case 'thingamajig':
return true;
default: return false;
}
}
// *****************************************************************************
// Perform the common logic for performing an HTTP request with the Drupal API
public function DrupalHttpRequest( $caller, $url, $method, $data, $includeAuthCookie = false ) {
$headers = array();
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
if ($includeAuthCookie) {
$headers['Cookie'] = $this->GetCookieHeader();
}
if ($data) {
$data = http_build_query($data, '', '&');
}
$response = drupal_http_request($url, $headers, $method, $data);
if ($response->code == 200) {
$response->data = json_decode($response->data);
}
else {
$response->data = NULL;
}
return $response;
}
// *****************************************************************************
public function Login( $username, $password ) {
$callerId = 'RemoteAPI_DrupalAPI->Login';
if (!$this->VerifyUnconnected( $callerId )) {
return NULL; // error
}
$url = $this->gateway.$this->apiVersion.'/user/login';
$data = array( 'username' => $username, 'password' => $password, );
$response = $this->DrupalHttpRequest($callerId, $url, 'POST', $data, false);
if ($response->code == 200) {
$this->session = $response->data->session_name;
$this->sessid = $response->data->sessid;
$this->status = RemoteAPI_status_loggedin;
return true; // meaning okay
}
return NULL; // meaning error
}
// *****************************************************************************
public function Logout() {
$callerId = 'RemoteAPI_DrupalAPI->Logout';
if (!$this->VerifyLoggedIn( $callerId )) {
return NULL; // error
}
$url = $this->gateway.$this->apiVersion.'/user/logout';
$response = $this->DrupalHttpRequest($callerId, $url, 'POST', NULL, true);
if ($response->code == 200) {
$this->status = RemoteAPI_status_unconnected;
$this->sessid = '';
$this->session = '';
return true; // success!
}
return NULL;
}
// **************************************************************************
// perform an 'Index' operation on a resource type using Drupal API.
// Return an array of resource descriptions, or NULL if an error occurs
public function Index( $resourceType ) {
$callerId = 'RemoteAPI_DrupalAPI->Index';
if (!$this->VerifyLoggedIn( $callerId )) {
return NULL; // error
}
$url = $this->gateway.$this->apiVersion.'/'.$resourceType;
$response = $this->DrupalHttpRequest($callerId, $url, 'GET', NULL, true);
return $response->data; // if failed, this is NULL, if success, this is an object holding requested data
}
// *****************************************************************************
// create a new resource of the named type given an array of data, using Drupal API
public function Create( $resourceType, $resourceData ) {
$callerId = 'RemoteAPI_DrupalAPI->Create:'.$resourceType;
if (!$this->VerifyLoggedIn( $callerId )) {
return NULL; // error
}
if (!$this->VerifyValidResourceType($resourceType)) {
return NULL;
}
$url = $this->gateway.$this->apiVersion.'/'.$resourceType;
$response = $this->DrupalHttpRequest($callerId, $url, 'POST', $resourceData, true);
return $response->data; // if failed, this is NULL, if success, this is an object holding response data
}
// **************************************************************************
// perform a 'GET' operation on the named resource type and id using Drupal API
public function Get( $resourceType, $resourceId ) {
$callerId = 'RemoteAPI_DrupalAPI->Get:'.$resourceType.'/'.$resourceId.'"';
if (!$this->VerifyLoggedIn( $callerId )) {
return NULL; // error
}
if (!$this->VerifyValidResourceType($resourceType)) {
return NULL;
}
$url = $this->gateway.$this->apiVersion.'/'.$resourceType.'/'.$resourceId;
$response = $this->DrupalHttpRequest($callerId, $url, 'GET', NULL, true);
return $response->data; // if failed, this is NULL, if success, this is an object holding response data
}
// *****************************************************************************
// update a resource given the resource type and updating array, using Drupal API
public function Update( $resourceType, $resourceData ) {
$callerId = 'RemoteAPI_DrupalAPI->Update:'.$resourceType;
if (!$this->VerifyLoggedIn( $callerId )) {
return NULL; // error
}
if (!$this->VerifyValidResourceType($resourceType)) {
return NULL;
}
if (!isset($resourceData['data']['id'])) {
_devReport('missing referencing ID of update resource!');
return NULL;
}
$url = $this->gateway.$this->apiVersion.'/'.$resourceType.'/'.$resourceData['data']['id'];
$response = $this->DrupalHttpRequest($callerId, $url, 'PUT', $resourceData, true);
return $response->data; // if failed, this is NULL, if success, this is an object holding response data
}
// *****************************************************************************
// perform a 'DELETE' operation on the named resource type and id using Drupal API
public function Delete( $resourceType, $resourceId ) {
$callerId = 'RemoteAPI_DrupalAPI->Delete:'.$resourceType;
if (!$this->VerifyLoggedIn( $callerId )) {
return NULL; // error
}
if (!$this->VerifyValidResourceType($resourceType)) {
return NULL;
}
$url = $this->gateway.$this->apiVersion.'/'.$resourceType.'/'.$resourceId;
$response = $this->DrupalHttpRequest($callerId, $url, 'DELETE', NULL, true);
return $response->data; // if failed, this is NULL, if success, this is an object holding response data
}
} // end of RemoteAPI_via_DrupalAPI object definition
?>
Example usage of communicating with a remote API
<?php
// create a new API communications object like this:
// the two parameters compose the URL the API communications take place
// (they are separate to facilitate API gateways and different API versions)
$apiObj = new RemoteAPI_DrupalAPI( 'http://api-project-clientName.apigee.com', // gateway
'/clientName/api/dev' // Services end point
);
// here's the logic to login. Required for authentication, and all other operations:
$apiObj->Login( 'joeuser', 'XXXXXXX' ); // pass in username and password
// if a resource type called "project" existed, a new project could be created like this:
$data = array( 'data' => array( 'title' => 'ApiDefinedProject',
'status' => 'active', // fantasy fields
'body' => 'demo information',
'comments' => 'disabled',
'tags' => '',
),
);
$ret = $apiObj->Create('project', $data );
// $ret will be NULL if an error occured, or will be an object with an 'id' field
// retrieve our new project:
$ret = $apiObj->Get('project', $ret->id); // <- 'id' used to get new project
// modifying the data for a modify:
$data['data']['body'] .= " can be modified too";
$data['data']['id'] = $ret->id; // <- so Update() knows what to modify
$ret = $apiObj->Update('project', $data );
// and delete just needs the id:
$ret = $apiObj->Delete('project', $ret->id );
// when completed working with the API, logout like this:
$apiObj->Logout();
?>
Help improve this page
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion