Developers »
Web Services » Here
Web Services Tutorial with PHP5 and standard SOAP libraries
Last revision: 2008-09-07
By Toby Champion
Target audience: developers, system administrators
In this tutorial, we will see how to use Lokad Web Services in a simple application written in PHP 5 and using the SOAP extension distributed with PHP 5. Lokad specializes in business time-series forecasting, but this document is neither an introduction to Lokad nor an introduction to Web Services.
Download: source-code for this tutorialRequirements
(*) The
Free subscription plan is sufficient to reproduce this tutorial, with the exception of the last section Retrieving forecasts which requires that the Enterprise plan be selected in your Lokad account.
Introduction
Web Services are an industrial-strength standard currently embraced by most (if not all) major software vendors. Lokad features Web Services: all operations that can be performed from the web application can also be performed programmatically through our Web Services. The Lokad Web Services can be reached at
Creating a SOAP client object using the WSDL
You should use
http://sandbox-ws.lokad.com/TimeSeries.asmx?wsdl as a test WSDL URL. The call below uses the production version. To use the production WSDL instead, remove the prefix
sandbox-.
$client = new SOAPClient('http://sandbox-ws.lokad.com/TimeSeries.asmx?wsdl');
Web Services Authentication
Lokad Web Services are
authenticated: to access our Web Services, as part of each method call you must provide the username and password associated with your Lokad Web Account. Lokad relies on
SOAP headers to authenticate each call.
You can check your headers will authenticate your SOAP requests by using the SOAP method
IsAuthenticated, which will return
true or
false depending on whether the call was authenticated. The code below calls this method and prints the result: it will be false, because we've not yet added the authentication information to the SOAP header.
try {
$result = $client->IsAuthenticated();
} catch (SoapFault $soapFault) {
die($soapFault);
}
printf("result = %d", $result->IsAuthenticatedResult); // This will be 0 (false)
Let's now add the authentication information. What we want to end up with is a header in the SOAP request that looks something like this:
<SOAP-ENV:Header>
<AuthHeader xmlns="lokad.com/ws">
<UserName>user@example.com</UserName>
<Password>password</Password>
</AuthHeader>
</SOAP-ENV:Header>
To do this, we can use the
__setSOAPHeaders method on the SOAP client to ensure each request will include this information in the header:
$username = 'email@example.com';
$password = 'mypassword';
$ns = "lokad.com/ws";
$auth = array();
$auth['UserName'] = new SOAPVar($username, XSD_STRING, null, null, null, $ns);
$auth['Password'] = new SOAPVar($password, XSD_STRING, null, null, null, $ns);
$headerBody = new SOAPVar($auth, SOAP_ENC_OBJECT);
$header = new SOAPHeader($ns, 'AuthHeader', $headerBody);
$client->__setSOAPHeaders(array($header));
We can now call the
IsAuthenticated again to check that we've got it right:
try {
$result = $client->IsAuthenticated();
} catch (SoapFault $soapFault) {
die($soapFault);
}
printf("result = %d\n", $result->IsAuthenticatedResult); // This will be 1 (true)
Time-series management
Lokad specializes in time-series forecasting. Before actually performing a forecast, you first need to upload your data; and this can be easily achieved with our Web Services. Technically, a time-series is simply an array of time-value pairs. Let's see how a time-series can be generated programmatically.
First, we'll create an array of time-value pairs, with
$length entries:
class TimeValue {
public function __construct($time, $value)
{
$this->Time = date('Y-m-d\TH:i:s', $time);
$this->Value = $value;
}
}
function randomTimeSerie($length)
{
$timeValues = array();
for ($i = 0; $i < $length; $i++) {
$times = mktime(0, 0, 0, date("m"), date("d") + $i, date("Y"));
$timeValues[$i] = new TimeValue($time, rand());
}
return $timeValues;
}
Note that we specify a
UtcOffset in the example above; this value is expressed in hours according to the formula
local time = UTC time + offset. In our example, for simplicity, the values are regularly spaced (one day increment for each value) but this is not a requirement. The intervals between the successive values can vary arbitrarily.
We'll now create a
TimeSerie object with our ten entries, and add this to your Lokad account.
Naming conventions: although the word serie (singular) does not exist in English, we have found it quite convenient to distinguish serie (singular) from series (plural) in the source code. So, when the term serie is used in our examples, it's not a mistake, it's intentional.
We'll define our
TimeSerie class, create an instance populated with our array, and call the
AddSerie Web Service method to add the time series to our account:
class TimeSerie {
public function __construct($name, $unitName, $utcOffset)
$this->Name = $name;
$this->UnitName = $unitName;
$this->UtcOffset = $utcOffset;
$this->TimeValues = null;
}
}
$timeSerie = new TimeSerie('TestSerie'.rand(), '', 0);
$timeSerie->TimeValues = array('TimeValue' => randomTimeSerie(10));
try {
$result = $client->AddSerie(array('serie' => $timeSerie));
} catch (SoapFault $soapFault) {
die($soapFault);
}
To delete a time-series from your account, you just need to specify the name identifier of the time-series in question:
try {
$result = $client->DeleteSerie(array('serieName' => 'My Time Series'));
} catch (SoapFault $soapFault) {
die($soapFault);
}
You can also retrieve the names of the existing time-series in your account; the following code outputs the names of the time-series in your account. We'll define a function,
to_array(), to handle the special cases of Lokad results coming back through the PHP 5 SOAP extension.
function to_array ($data) {
if (!property_exists($data, 'string')) // 0 elements
return array();
else if (is_string($data)) // 1 element
return array($data);
else // >1 elements
return $data->string;
}
try {
$result = $client->GetAllSerieNames();
} catch (SoapFault $soapFault) {
die($soapFault);
}
foreach (to_array($result->GetAllSerieNamesResult) as $key => $name)
echo "$name\n";
You can retrieve the time-value pairs in a particular series as follows:
try {
$result = $client->GetSerie(array('serieName' => $timeSerie->Name));
} catch (SoapFault $soapFault) {
die($soapFault);
}
printf("Here is the data in %s:\n\n", $timeSerie->Name);
printTimeValues($result->GetSerieResult->TimeValues->TimeValue);
function printTimeValues($timeValues)
{
foreach ($timeValues as $timeValue)
printf("%s %d\n", $timeValue->Time, $timeValue->Value);
}
Forecasting task management
Before you can retrieve a time-series forecast, you need to define a forecasting task that will specify how the forecast time-series should be computed. In the Lokad data model, a forecasting task is always associated with a particular time-series; several forecasting tasks can be associated with the same time-series.
class Task {
public function __construct($name, $aggregator, $period, $pastPeriods,
$futurePeriods, $periodStart))
{
$this->Name = $name;
$this->Aggregator = $aggregator;
$this->Period = $period;
$this->PastPeriods = $pastPeriods;
$this->FuturePeriods = $futurePeriods;
$this->PeriodStart = $periodStart;
}
}
$task = new Task('TestTask'.rand(), 'Sum', 'Day', 10, 5, '2001-01-01T00:00:00');
Tip: the time-series names should be unique within the scope of your Lokad account (you cannot have two distinct time-series identified by the same name). However, the names of the forecasting tasks only need to be unique within the scope of their base time-series. In other words, you can have several forecasting tasks identified by the same name but associated with different time-series.
Now we add the task.
try {
$result = $client->AddTask(array('serieName' => $timeSerie->Name,
'task' => $task));
} catch (SoapFault $soapFault) {
die($soapFault);
}
Retrieving forecasts
The following code will retrieve the forecasts for the task previously created.
try {
$result = $client->GetSerie(array('serieName' => $timeSerie->Name));
} catch (SoapFault $soapFault) {
die($soapFault);
}
printTimeValues($result->GetSerieResult->TimeValues->TimeValue);
try {
$result = $client->GetForecast(array('serieName' => $timeSerie->Name,
'taskName' => $task->Name));
} catch (SoapFault $soapFault) {
die($soapFault);
}
printTimeValues($result->GetForecastResult->TimeValues->TimeValue);