Wednesday, 12 June 2013

Calling WCF SOAP web services from PHP

Why would I want to? There are two reasons why this might be necessary, firstly, there might be a third-party web service that is SOAP and secondly, as in my case, I need a PHP web service but it needs to use functionality that does not readily exist in PHP but does in .Net - so I will call a .Net web service to do the heavy lifting.

In my case, the requirements are:
  1. Fairly standard WCF web service
  2. Transport security (https)
  3. Client certificate security
  4. SOAP 1.2
  5. PHP 5.3 SOAP client
Now, I eventually got this to work using the code below but by all means try and remove some of the options to see if they are already set. As for me, I kept making changes until it worked.

$protected_url = "https://path.to.your/WebService.svc?wsdl";
$my_cert_file = Yii::getPathOfAlias( 'webroot' ) . "/login.pem";

$client = new SoapClient($protected_url, array('local_cert' => $my_cert_file, 
                                              'soap_version' => SOAP_1_2,
                                              'trace' => TRUE) );
$actionHeader = array( new SoapHeader('http://www.w3.org/2005/08/addressing',
                      'Action',
                      'http://path.to.your/namespace/interfacename/CheckImage'),
                      new SoapHeader('http://www.w3.org/2005/08/addressing',
                      'To',
                      'https://path.to.your/WebService.svc/endpointname')
                      );
$client->__setSoapHeaders($actionHeader);
try
{
    $something = $client->CheckImage(array('userId' => "23",'imagename' => 'test'));
}
catch(Exception $e)
{
   $this->sendResponse(500, $client->__getLastRequest(),FALSE);
}
$this->sendResponse(200, $something);

So, what's going on? The first url is where the soap client will retrieve the WSDL from. In WCF, the standard location is the service name with ?wsdl on the end. There is also a similar wsdl with ?singleWsdl on the end but I don't know the difference (maybe this was my problem!).

The second line, in my case, is just building a path to a locally installed client certificate (in my case, I am using Yii).

When creating the client, I have to specify the client certificate and the soap version in the options array. I also set trace to TRUE and this allows me to use __getLastRequest() later on if I have problems.

I had various SOAP errors and these were related to missing headers which meant the endpoint filters were complaining, so I specify these as additional headers. If you search your wsdl for your function , you will find an Action attribute that has this information in it. For the "To" header, similarly, you can find this under the relevant endpoint as the value of
(which might also have a namespace like wsa10:Address).


The try block calls the name of my function and passes in an associative array of parameters. This allows the endpoint to marshall them to the correct positions in the function.

In my case, if an error occurs, I return the value of __getLastRequest() to the caller and this returns the XML that was sent to the web service. This is really helpful when things don't seem to work because you can compare it with known good calls that you could make from .Net to .Net.

sendResponse is just a helper method that I use to return HTTP codes.
Post a Comment