SOAP stands for Simple Object Access protocol. When accessing webservices in PHP the choice is either SOAP or REST. SOAP is around for longer (since 1998) but REST is more popular. Check php.ini if PHP is configured with ‘–enable-soap’

A public SOAP server exists, webservicex.net, that can be used to test SOAP connections. All it requires is the publicly accessible WSDL:

$wsdl = "http://www.webservicex.net/ConvertWeight.asmx?WSDL";
$client = new SoapClient($wsdl);
$params = array(
'Weight' => 8, 
'FromUnit' => 
'Kilograms', 
'ToUnit' => 'Grams');
$conversion = $client->ConvertWeight($params);

echo $conversion->ConvertWeightResult; // prints 8000

The wsdl (pronounce wizdul) file explains what methods are available with what arguments. In this webservice the method is ConvertWeight that takes in any quantity of a unit of weight and returns the equivalent in another weight unit.

To find out what happens behind the scenes switch to debug-mode with option trace = 1 and making sure the xml output is readable:

$options = array(
    'trace' => 1
);

$wsdl = "http://www.webservicex.net/ConvertWeight.asmx?WSDL";

$client = new SoapClient($wsdl, $options);

$params = array(
    'Weight' => 8,
    'FromUnit' => 'Kilograms',
    'ToUnit' => 'Grams'
);

$conversion = $client->ConvertWeight($params);

echo $client->__getLastRequestHeaders();

$xml = $client->__getLastRequest();


// make sure xml output is human-readable
$dom = new DOMDocument("1.0");
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;

$dom->loadXML($xml);
echo htmlentities($dom->saveXML()) ;

The trace option gives access to __getLastRequestHeaders() and __getLastRequest() and the headers are:

POST /ConvertWeight.asmx HTTP/1.1
Host: www.webservicex.net
Connection: Keep-Alive
User-Agent: PHP-SOAP/5.4.7
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://www.webserviceX.NET/ConvertWeight"
Content-Length: 343

This is what the response looks like using __getLastResponseHeaders() and __getLastResponse()

HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Content-Length: 377
Content-Type: text/xml; charset=utf-8
Server: Microsoft-IIS/7.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET

We can sort of guess what action takes place on the server side:

# filename UnitConverter.php

class UnitConverter
{

    /**
     *
     * @param int $weight
     * @param string $fromUnit
     * @param string $toUnit
     * @return float
     */
    public function convert($weight, $fromUnit, $toUnit)
    {
        $a = array('Kilograms' => 1000, 'Grams' => 1);
        return $weight * ( $a[$fromUnit] / $a[$toUnit] );
    }

}

First step is creating a class for storing the methods. Second step is creating a wsdl document but this file has a difficult syntax. Zend Framewerk provides help with Zend_Soap_AutoDiscover:

#filename wsdl.php

defined('APPLICATION_PATH') 
|| define('APPLICATION_PATH', 
realpath(dirname(__FILE__) . '/../application'));

set_include_path(implode(PATH_SEPARATOR, array(
    realpath(APPLICATION_PATH . '/../library'),
    get_include_path(),
)));

require_once 'Zend/Loader/Autoloader.php';
$loader = Zend_Loader_Autoloader::getInstance();

include('UnitConverter.php');

$autodiscover = new Zend_Soap_AutoDiscover;
$autodiscover->setClass('UnitConverter')
        ->setUri('http://unit-converter.local/soapServer.php');

$wsdl = $autodiscover->handle();

The third step is creating the soap server:

ini_set("soap.wsdl_cache_enabled", "0");
include('UnitConverter.php');

$wsdl = 'http://unit-converter.local/wsdl.php';
$server = new SOAPServer($wsdl);
$server->setObject(new UnitConverter());
$server->handle();

and in the final step the soap client is created:

$wsdl = 'http://unit-converter.local/wsdl.php';
$client = new SoapClient($wsdl);
echo $client->convert(8, 'Kilograms', 'Grams');

Returning the response as an object as in the webservicex.net use case is not trivial.

$xml = simplexml_load_string($response);
$xml->registerXPathNamespace('soap', 'http://schemas.xmlsoap.org/soap/envelope/');
$body = $xml->xpath('//soap:Body');
$body = $body[0];
echo $body->ConvertWeightResponse->ConvertWeightResult;

The result itself can be an object by modifying the server-side UnitConverter object:

class UnitConverter
{

    /**
     *
     * @var float
     */
    public $convertWeightResult;

    /**
     *
     * @param int $weight
     * @param string $fromUnit
     * @param string $toUnit
     * @return UnitConverter
     */
    public function convert($weight, $fromUnit, $toUnit)
    {
    $a = array('Kilograms' => 1000, 'Grams' => 1);
    $this->convertWeightResult = $weight * ( $a[$fromUnit] / $a[$toUnit] );
    return $this;
    }

Make sure the $convertWeightResult property is defined as a float in the docbloc. Also make sure the convert method returns the object itself.

$wsdl = 'http://unit-converter.local/wsdl.php';
$client = new SoapClient($wsdl);
$conversion = $client->convert(8, 'Kilograms', 'Grams');
echo $conversion->convertWeightResult;

Is it possible to do without the WSDL document? In the non-WSDL mode all that is required is the location of the soap server in the $options array:

$options = array(
'uri' => 'blablabla',
'location' => 'http://unit-converter.local/soapServer.php'
);
$client = new SoapClient(null, $options);
echo $client->convert(8, 'Kilograms', 'Grams');

The purpose of the uri is vague, it is mandatory but the only requirement is that it is unique. For now ‘blablabla’ will do.

Is it possible to access the ASP-based webservicex.net SOAP service in non-WSDL mode? Trying:

$options = array(
'uri' => 'http://www.webserviceX.NET/',
'location' => 'http://www.webservicex.net/ConvertWeight.asmx',
);
$client = new SoapClient(null, $options);
$params = array(
'Weight' => 8, 
'FromUnit' => 
'Kilograms', 
'ToUnit' => 'Grams');
$conversion = $client->ConvertWeight($params);

results in a vague ” Server did not recognize the value of HTTP Header SOAPAction: webserviceX.NET/#ConvertWeight.” error.

WSDL file

The wsdl xml file describes the webservice. The opening definitions tag mentions the endpoint twice: in the atributes xml:tns and  targetNamespace. In fact the endpoint shows up all over the place. It turns out only the soap:address endpoint in the service section is relevant. In fact all other instances of the endpoint can be safely replaced by any string, for example unit.

 

Links

http://www.whitewashing.de/2014/01/31/soap_and_php_in_2014.html

Advertisements