The solution described is largely based on Rossoft work and nusoap project (for the PHP4 compatibility).
There are two parts, a Cake application acting as a server and serving request as SOAP request. Then a sample client that demonstrate the request to the SOAP server (actually this can be any Soap server).
The server.
The idea is to use one controller that will act and handle all the soap request. In this controller we will describe the SOAP "services" as well as their input/output data.
The nusoap vendor should be placed in the vendors/nusoap folder and it contains
- the refactored nusoap files (necessary for PHP4 and renamed for compatibility with PHP5)
- a base controller called "WebServicesController", derived from AppCOntroller and handling our soap request.
(check later for downloading the vendors/nusoap files).
Let's say we want our controller being "servers", we will then declare it as follow :
[php]
App::import('Vendor', 'WebServicesController');
class ServersController extends WebServicesController
{
var $name = 'Servers';
var $uses = array('Person');
var $autoRender = false;
var $api=array(
'simpletest' => array('doc' => 'Simple test, gets an integer from input, return integer * 2',
'input' => array('appid' => 'xsd:integer'),
'output' => array('return' => 'xsd:integer')),
'sumtest' => array('doc' => 'Sum test, gets 2 integer return the sum',
'input' => array('int1' => 'xsd:integer', 'int2' => 'xsd:integer'),
'output' => array('return' => 'xsd:integer')),
)
// simpletest, just get an integer and return it's double
function simpletest($inp)
{
return $inp * 2;
}
// get 2 integers and return the sum
function sumtest()
{
$v = func_get_args();
if(count($v) != 2)
$this->_soap_server->fault(20, '', 'Wrong number of parameters passed, Expected 2');
else
return array_sum($v);
}
}
[/php]
Note that the uses is there just for the testing application (available in download). The important parts in this controller:
- autoRender = false, this is important since anything rendered as "html" will make the SOAP xml inconsistent.
- api, these are our handled soap services. Api get 3 parameters: "doc", "input", "output". The api keys of the array are matching a method of our controller, these methods handle the data part.
- doc, a short description of the service
- input, the input parameters service. The data received by the method can be simple (as described) or complex (see below)
- output, usually a "return" statement that contains either a simple type or a complex type (see below)
The complex type are described by the class variable $complexType as follow :
[php]
var $complexTypes = array('twoint' => array('struct' => array('int1' => 'xsd:integer',
'int2' => 'xsd:integer')),
'intarray' => array('array' => 'xsd:integer'),
'fullperson' => array('model' => 'Person'),
'cleanfullperson' => array('model' => 'Person', 'ignoreFields' => array('created', 'modified')),
'firstnameperson' => array('model' => 'Person', 'onlyFields' => array('firstname')),
'firstnamepersonservertime' => array('struct' => array(array('model' => 'Person', 'onlyFields' => array('firstname')),
'servertime' => 'xsd:string')),
'allpersons' => array('array' => 'fullperson'),
'allpersonsfirstname' => array('array' => 'firstnameperson'),
);
[/php]
few tips:
Complex have a name, then a type "array", "struct" and I have added "model". This will build up the SOAP data part par method. Then a method can get or return a complex type or nested complex type:
[php]
'getallpersons' => array('doc' => 'Retrieve All Person records',
'output' => array('return' => 'allpersons')),
[/php]
Note that for model, I have added the "ignoreFields" property and "onlyFields", this can be usefull if you want to restrict only few name of a model, or only include few names of the model.
The sample code attached (soapserver) is full of example and contains a database for testing (only 3 records). The DB script is located in the zip file as well.
After installing the sample, type "http://localhost/soapserver/servers" in a browser, that should list a description of all methods exposed by the server.
The client part
In order to query a soap server, I did a small component. The client also needs the vendors/nusoap folder. However it does not use the WebservicesController.
To make a request to a SOAP server, a client should include
[php]
var $components = array('soap');
[/php]
To handle a SOAP request, this would be done as follow :
[php]
function testsoap()
{
$url = Configure::read('SoapServer');
$func = 'simpletest';
$snd = array('appid' => 15);
$data = $this->soap->client($url, $func, $snd, false);
$this->_renderResult($data);
$this->set('data',$data);
}
[/php]
This is extract from the client sample application (attached as zip). Be sure to set the correct "SoapServer" url in the core/config.php file.
The rest is quite straight forward. Check the code, and the samples handled by the client (the name is matching the server methods). I did not optimize all code, since it's for demo purpose.
Running the server sample application
- The application are running on cake1.2 from the SVN branch (should work with the official Beta release)
- Unzip the nusoap.zip in the vendors directory
- Unzip the soapserver.zip to create your soapserver application.
- create a database (default is tutusoap), there is a SQL script in the zip
- Adjust your DB host/user/password in /config/database.php
- try to call your application using the controller "servers" in a browser.
Running the client sample application
- Unzip the nusoap.zip in vendors (can be already done if vendors is as cake level)
- Unzip the soapclient.zip to create the soapclient application.
- edit the /config/config.php and change the SoapServer parameter to fit your soap server location.
- access your soap client application, it routes directly to an home made home page (derived from the cake core). The client does not need database, just ignore the warning about database config not found.
Here are the files. Remarks, comments are welcome.