WebServices are very usefull if you need an common interface between external applications and your software. Especially WSDL driven WebServices are widely supported by many programming languages. Some SDKs offer possibilities to automatically create classes, functions and objects that you need to interact with the WSDL WebService.
The WSDL File
The WSDL File is an xml file that contains all information about the interface.
So we will find here information about the functionality the interface offers and how to access it. It also delivers the definition of parameters and return values required/delivered by the interface.
<?xml version ='1.0' encoding ='iso-8859-1' ?>
<wsdl:definitions
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
xmlns:tns="http://customer-ws.tld/customer-ws_Customers.wsdl"
xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="http://customer-ws.tld/customer-ws_Customers.wsdl"
name="customer-WS">
The definitions are requiered by the clients who access the WSDL-File to get information about the structure of the WSDL file and how to parse it.
targetNamespace and
xmlns:tns are set to the URL (in my case: http://customer-ws.tld/customer-ws_Customers.wsdl) of the WSDL file.
Complex Types
<wsdl:types>
<s:schema
targetNamespace="http://customer-ws.tld/customer-ws_Customers.wsdl"
xmlns:tns="http://customer-ws.tld/PAF/customer-ws_Customers.wsdl">
<s:complexType name="Customer">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="customerNr" type="s:string"/>
<s:element minOccurs="1" maxOccurs="1" name="salutation" type="s:string"/>
<s:element minOccurs="1" maxOccurs="1" name="title" type="s:string"/>
<s:element minOccurs="1" maxOccurs="1" name="firstname" type="s:string"/>
<s:element minOccurs="1" maxOccurs="1" name="lastname" type="s:string"/>
<s:element minOccurs="1" maxOccurs="1" name="phone" type="s:string"/>
<s:element minOccurs="1" maxOccurs="1" name="fax" type="s:string"/>
<s:element minOccurs="1" maxOccurs="1" name="email" type="s:string"/>
<s:element minOccurs="1" maxOccurs="1" name="postalZip" type="s:string"/>
<s:element minOccurs="1" maxOccurs="1" name="postalCityName" type="s:string"/>
<s:element minOccurs="1" maxOccurs="1" name="postalStreet" type="s:string"/>
<s:element minOccurs="1" maxOccurs="1" name="postalStreetNumber" type="s:string"/>
</s:sequence>
</s:complexType>
<s:complexType name="CustomerArray">
<s:complexContent>
<s:restriction base="soapenc:Array">
<s:attribute ref="soapenc:arrayType" wsdl:arrayType="tns:Customer[]"/>
</s:restriction>
</s:complexContent>
</s:complexType>
</s:schema>
</wsdl:types>
The WSDL delivers simple data types like int, string etc. but in most cases we need much more complex data structures that have to be returned by our service. We have to define complex data types in the WSDL xml file. We define an object called 'Customer' and a type 'CustomerArray' which is an array consists of 'Customer' objects.
The Customer objects has the following attributes:
- customerNr
- salutation
- title
- firstname
- lastname
- phone
- fax
- email
- postalZip
- postalCityName
- postalStreet
- postalStreetNumber
Methods
Now it's time to define the methods our WebService should offer. For now we are creating 3 different methods:
1. getCustomers()
It returns an array of Customer objects.
2. getCustomer(aCustomerId)
It returns the customer with the given 'aCustomerId' as an Customer object
3. sendCustomer(aCustomer)
It sends a Customer Object to the server and returns the new Customer object.
<wsdl:message name='getCustomersRequest'>
</wsdl:message>
<wsdl:message name='getCustomersResponse'>
<wsdl:part name='Result' type='tns:CustomerArray'/>
</wsdl:message>
<wsdl:message name='getCustomerRequest'>
<wsdl:part name='aCustomerId' type='s:string'/>
</wsdl:message>
<wsdl:message name='getCustomerResponse'>
<wsdl:part name='Result' type='tns:Customer'/>
</wsdl:message>
<wsdl:message name='sendCustomerRequest'>
<wsdl:part name='aCustomer' type='tns:Customer'/>
</wsdl:message>
<wsdl:message name='sendCustomerResponse'>
<wsdl:part name='Result' type='tns:Customer'/>
</wsdl:message>
<wsdl:portType name='CustomerPortType'>
<wsdl:operation name='getCustomers'>
<wsdl:input message='tns:getCustomersRequest'/>
<wsdl:output message='tns:getCustomersResponse'/>
</wsdl:operation>
<wsdl:operation name='getCustomer'>
<wsdl:input message='tns:getCustomerRequest'/>
<wsdl:output message='tns:getCustomerResponse'/>
</wsdl:operation>
<wsdl:operation name='sendCustomer'>
<wsdl:input message='tns:sendCustomerRequest'/>
<wsdl:output message='tns:sendCustomerResponse'/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name='ServiceSoap' type='tns:CustomerPortType'>
<soap:binding style='rpc' transport='http://schemas.xmlsoap.org/soap/http'/>
<wsdl:operation name='getCustomers'>
<soap:operation soapAction='urn:customer-ws.tld-scramble#getCustomers'/>
<wsdl:input>
<soap:body use='encoded' namespace='urn:customer-ws.tld-scramble'
encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
</wsdl:input>
<wsdl:output>
<soap:body use='encoded' namespace='urn:customer-ws.tld-scramble'
encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name='getCustomer'>
<soap:operation soapAction='urn:customer-ws.tld-scramble#getCustomer'/>
<wsdl:input>
<soap:body use='encoded' namespace='urn:customer-ws.tld-scramble'
encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
</wsdl:input>
<wsdl:output>
<soap:body use='encoded' namespace='urn:customer-ws.tld-scramble'
encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name='sendCustomer'>
<soap:operation soapAction='urn:customer-ws.tld-scramble#sendCustomer'/>
<wsdl:input>
<soap:body use='encoded' namespace='urn:customer-ws.tld-scramble'
encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
</wsdl:input>
<wsdl:output>
<soap:body use='encoded' namespace='urn:customer-ws.tld-scramble'
encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
WSDL method definitions always consists of two parts:
- Request
- Response
The request defines the handle of the data send to the server and the response the resultset.
Service-Definition
The service definition describes how the WebService is accessed and where the server component is located.
<wsdl:service name='customer-WS'>
<wsdl:port name="ServiceSoap" binding="tns:ServiceSoap">
<soap:address location='http://customer-ws.tld/server.php'/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
The SOAP server
First of all we create a new PHP class that extends the SoapServer class delivered with PHP (if compiled in: for more details see
here):
MySoapServer.class.php
<?php
if(class_exists('SoapServer')){
class Customer_SoapServer extends SoapServer {
}
}
?>
Now we create the Customer class that we previously defined in the WSDL file:
Customer.class.php
<?php
class Customer {
public $customerNr;
public $salutation;
public $title;
public $firstname;
public $lastname;
public $phone;
public $fax;
public $email;
public $postalZip;
public $postalCityName;
public $postalStreet;
public $postalStreetNumber;
}
?>
Then we create the server script, that initializes the SOAP server and supplies the functions we have defined in the WSDL file:
server.php
<?php
include('Customer_SoapServer.class.php');
include('Customer.class.php');
public function getCustomers(){
$vCustomers = CustomerUtility::getAllCustomersArray();
$vReturnArray = array();
foreach($vCustomers as $vCustomerId => $vCustomer){
$vWSCustomer = getWSCustomer($vCustomer);
$vReturnArray[] = $vWSCustomer;
}
return $vReturnArray;
}
public function getWSCustomer($vCustomer){
$vWSCustomer = new Customer();
$vWSCustomer->customerNr = $vCustomer['id_user'];
$vWSCustomer->salutation = $vCustomer['salutation'];
$vWSCustomer->title = $vCustomer['title'];
$vWSCustomer->firstname = $vCustomer['firstname'];
$vWSCustomer->lastname = $vCustomer['lastname'];
$vWSCustomer->phone = $vCustomer['tel'];
$vWSCustomer->fax = $vCustomer['fax'];
$vWSCustomer->email = $vCustomer['email'];
$vWSCustomer->postalZip = $vCustomer['postal_zip'];
$vWSCustomer->postalCityName = $vCustomer['postal_city'];
$vWSCustomer->postalStreet = $vCustomer['postal_street'];
$vWSCustomer->postalStreetNumber = $vCustomer['postal_streetnumber'];
return $vWSCustomer;
}
public function getCustomer($aCustomerNr){
$vWSCustomer = getWSCustomer(CustomerUtility::getCustomerArrayByCustomerNr($aCustomerNr));
return $vWSCustomer;
}
public function sendCustomer($aWSCustomer){
$vCustomer = CustomerUtility::storeCustomer($aWSCustomer);
return getCustomer($aWSCustomer->customerNr);
}
// turn off the wsdl cache
ini_set("soap.wsdl_cache_enabled", "0");
$vServer = new Customer_SoapServer("customer-ws_Customers.wsdl");
$vServer->addFunction("getCustomers");
$vServer->addFunction("getCustomer");
$vServer->addFunction("sendCustomer");
$vServer->handle();
?>
I've left out the actions inside the CustomerUtility because anything can be done here: fetching/storing from/to database etc.
The SOAP client
Now we create the SOAP client script that fetches the data from the WSDL file and connects to the soap server and fetches the data.
client.php:
<?php
include('Customer.class.php');
// turn off the WSDL cache
ini_set('soap.wsdl_cache_enabled', '0');
$vLocation = 'http://customer-ws.tld/server.php';
try {
$vSoapClient = new SoapClient('http://customer-ws.tld/customer-ws_Customers.wsdl',
array(
"location" => $vLocation,
"trace"=>1, "exceptions"=>1
)
);
$vFunctions = $vSoapClient->__getFunctions();
echo "<pre>";
var_dump($vFunctions);
echo "</pre>";
$vCustomerObjectArray = $vSoapClient->__soapCall( 'getCustomers', array());
echo "<pre>";
var_dump($vCustomerObjectArray);
echo "</pre>";
$vSingleCustomerObject = $vCustomerObjectArray[0];
echo "<pre>";
var_dump($vSingleCustomerObject);
echo "</pre>";
$vSingleCustomerObject->lastname = "My-Changed-Lastname";
$vSendCustomerObject = $vSoapClient->__soapCall( 'sendCustomers', array($vSingleCustomerObject));
echo "<pre>";
var_dump($vSendCustomerObject);
echo "</pre>";
}catch (SoapFault $vException) {
echo "Error:<br />" . nl2br($vException->faultcode) . '<br /><br />Error Details:<br />'. nl2br($vException->faultstring) . '<br />';
echo("<br />REQUEST :<br />" . htmlspecialchars($vSoapClient->__getLastRequest()) . "<br />");
echo("<br />RESPONSE:<br />" .htmlspecialchars($vSoapClient->__getLastResponse()) . "<br />");
}
?>
After we connect to the server, we display all functions (__getFunctions) available by the SOAP webservice. Then we call the getCustomers function and the server responds with an array of customers and we take out the first customer, change its lastname to "My-Changed-Lastname" and send this object back to the server.