I have just uploaded a PHP library for importing and exporting OpenDD documents. It is version 0.1 so it is still very much under development, but it should still be usable.

Import example

The import function accepts an OpenDD XML document and returns an ODDDocument object, which can then be iterated through to obtain access to the ODD* classes.

The XML file:

<odd version="1.0" generated="Fri, 09 May 2008 18:53:37 +0100">
<entity uuid="http://www.example.com/a" class="foo" subclass="http://www.example.com/b" />
<metadata uuid="http://www.example.com/c" entity_uuid="http://www.example.com/a" name="Squid" >paper</metadata>
<relationship uuid1="http://www.example.com/a" type="hates" uuid2="http://www.example.com/b" />
</odd>

Example code to process it:

$doc = ODD_Import($in);

foreach ($doc as $element)
print_r($element); // Just echo, but you should probably do something meaningful.

Export example

Export involves constructing an ODDDocument object and populating it with various classes, each one representing one of the OpenDD elements.

This object is then serialised.

Here is some example code:

$out = new ODDDocument(); // Create new document

$entity = new ODDEntity("http://www.example.com/a", "foo", "http://www.example.com/b");
$out->addElement($entity);

$meta = new ODDMetaData("http://www.example.com/c", "http://www.example.com/a", "Squid", "paper");
$out->addElement($meta);

$rel = new ODDRelationship("http://www.example.com/a", "hates", "http://www.example.com/b");
$out->addElement($rel);

echo ODD_Export($out); // Export

When I get a chance I’ll upload some libraries in other languages, but if you feel inclined then feel free to implement your own!

In yesterday’s article, we took a look at the server’s reply format. You now have all the information you need to construct the core of a library and start making server requests.

Here is some sample PHP code for making POST and GET style queries. This library will let you make a query, and return a stdClass object containing the serialised fields as described yesterday, from this you can build wrapper functions for the methods.

$API_CLIENT = new stdClass;

// Configure your client
$API_CLIENT->api_key = "your api key";
$API_CLIENT->secret = "your secret key";
$API_CLIENT->api_endpoint = "http://elggvoices.com/api/v1/";
$API_CLIENT->hmac_algo = 'sha256';
$API_CLIENT->postdata_hash_algo = 'md5';

// Status variables we can query later
$LAST_CALL = null;
$LAST_CALL_RAW = "";
$LAST_ERROR = null;

/**
* Generate our HMAC.
*/
function calculate_hmac($algo, $time, $api_key, $secret_key, $get_variables, $post_hash = "")
{
$ctx = hash_init($algo, HASH_HMAC, $secret_key);
hash_update($ctx, trim($time));
hash_update($ctx, trim($api_key));
hash_update($ctx, trim($get_variables));
if (trim($post_hash)!="") hash_update($ctx, trim($post_hash));
return hash_final($ctx);
}

/**
* Generate our POST hash.
*/
function calculate_posthash($postdata, $algo)
{
$ctx = hash_init($algo);
hash_update($ctx, $postdata);
return hash_final($ctx);
}

/**
* Serialise HTTP headers.
*/
function serialise_headers(array $headers)
{
$headers_str = "";
foreach ($headers as $k => $v)
$headers_str .= trim($k) . ": " . trim($v) . "\r\n";
return trim($headers_str);
}

/**
* Make a raw call.
* @param array $method Method call parameters.
* @param string $postdata Optional POST data.
* @param string $content_type The content type.
* @return stdClass
*/
function call(array $method, $postdata = "", $content_type = 'application/octet-stream')
{

// Get the config
global $API_CLIENT, $LAST_CALL, $LAST_CALL_RAW, $LAST_ERROR;

$headers = array();
$encoded_params = array();

$time = microtime(true); // Get the current time in microseconds
$request = ($postdata!="" ? "POST" : "GET"); // Get the request method, either post or get

// Hard code the format - we're using PHP, so lets use PHP serialisation.
$method['format'] = "php";

// URL encode all the parameters
foreach ($method as $k => $v){
$encoded_params[] = urlencode($k).'='.urlencode($v);
}

$params = implode('&', $encoded_params);

// Put together the query string
$url = $API_CLIENT->api_endpoint."?". $params;

// Construct headers
$posthash = "";
if ($request=='POST')
{
$posthash = calculate_posthash($postdata, $API_CLIENT->postdata_hash_algo);
$headers['X-Searunner-posthash'] = $posthash;
$headers['X-Searunner-posthash-algo'] = $API_CLIENT->postdata_hash_algo;
$headers['Content-type'] = $content_type;
$headers['Content-Length'] = strlen($postdata);
}

$headers['X-Searunner-apikey'] = $API_CLIENT->api_key;
$headers['X-Searunner-time'] = $time;
$headers['X-Searunner-hmac-algo'] = $API_CLIENT->hmac_algo;
$headers['X-Searunner-hmac'] = calculate_hmac($API_CLIENT->hmac_algo,
$time,
$API_CLIENT->api_key,
$API_CLIENT->secret,
$params,
$posthash
);

// Configure stream options
$opts = array(
'http'=>array(
'method'=> $request,
'header'=> serialise_headers($headers)
)
);

// If this is a post request then set the content
if ($request=='POST')
$opts['http']['content'] = $postdata;

// Set stream options
$context = stream_context_create($opts);

// Send the query and get the result and decode.
$LAST_CALL_RAW = file_get_contents($url, false, $context);
$LAST_CALL = unserialize($LAST_CALL_RAW);

if (($LAST_CALL) && ($LAST_CALL->status!=0)) // Check to see if this was an error
$LAST_ERROR = $LAST_CALL;

return $LAST_CALL; // Return a stdClass containing the API result
}

// Example GET call
$get = call(
array (
'method' => 'test.test',
'variable1' => 1,
'variable2' => "test string"
)
);

// Example POST call
$post = call(
array (
'method' => 'test.test',
'variable1' => 1,
'variable2' => "test string"
),
"Some post data"
);

// Output
echo "Example GET result: \n";
print_r($get);

echo "Example POST result: \n";
print_r($post);

On Friday we discussed how to construct an API request, today we will discuss how to handle the response that the server returns.

Server replies

The elggVoices server will respond with a result packet containing a number of fields. The format of this reply depends on the format requested. From the manual:

  • ‘status’: The status code, 0 for success or a non-zero error code.
  • ‘message’: If the status code is non-zero this contains a human readable
    explanation.
  • ‘result’: The mixed result of the execution, this may be a simple type
    (int, string etc), an array or even an object, depending on the API being
    called (see also result objects).
  • ‘runtime_errors’: If there have been any runtime errors picked up by
    the internal error handler during the execution of the command, these
    are given here in an array.

The server result will be returned, serialised in the requested format (xml,php or json).

Errors will often give details pointing to what went wrong, these can be helpful when debugging your application or when looking for support on the forums.

Have a look at this XML formatted error message, XML success messages will be of a similar format:

<SearunnerResult>
<status type="integer">-1</status>
<message type="string">Missing X-Searunner-apikey HTTP header</message>
<result type="string">exception 'APIException' with message 'Missing X-Searunner-apikey HTTP header' in /home/liveshouts/html/lib/api/validation.php:109
Stack trace:
#0 /home/liveshouts/html/api/endpoints/rest.php(19): get_and_validate_api_headers()
#1 {main}</result>
<runtime_errors type="array">
<array_item name="0" type="string" >DEBUG: 2008-02-24 18:38:19 (UTC): & quot;Undefined index: method& quot;
in file /home/liveshouts/html/lib/engine/input.php (line 30)</array_item>
<array_item name="1" type="string">DEBUG: 2008-02-24 18:38:19 (UTC): &quot;Undefined index: HTTP_X_SEARUNNER_APIKEY&quot; in file /home/liveshouts/html/lib/api/validation.php (line 107)</array_item>
</runtime_errors>
</SearunnerResult>