PHP 5 Web Service Client

Until yesterday, I’d never written a single line of PHP. In fact, if you’ve read much of this blog you’ll see that most of what I do is c# and SQL Server related. Many of my posts are an attempt to simplify something that ought to have been easy but turned out to take 5 or 10 times as long as I thought it would. That was definitely the case with PHP and web services. Below are the steps that take you from a blank slate (PHP-wise) to successfully calling a .net web service using complex and composited types. In case you’re wondering why I wrote a web service client in PHP, a client was having problems accessing our web service using PHP.

My first step was to acquire and install the Uniform Server. I installed from UniServer3_5.exe. Uniform Server would have worked great if I didn’t already have IIS running on port 80. I’ve done a normal Apache install before under these circumstances, and Apache very kindly tells you the problem and how to fix it. Unfortunately, Uniform Server hides these problems. Basically, it doesn’t work and you have no idea why. If you do need to change the port, you’ll need to edit the http.conf under ..\udrive\usr\local\apache2\conf. “..\” refers to the directory where you extracted the Uniform Server (it can be anywhere you like). Search on 80 and replace with 8080 or whatever you want. The three lines I changed were:

Listen 8080
ServerName localhost:8080
<VirtualHost *>
	ServerName localhost:8080

Note that even after doing this, Start_Server will still try to take you to a URL without the port specified.

Next, you will need install and enable the Soap extension which you can find in here as ext\php_soap.dll. Copy this file to ..\udrive\usr\local\php\extensions. Now you will need to enable the extension by adding the following line to your ..\udrive\usr\local\php\php.ini:

extension=php_soap.dll

Save the changes and then stop and start the server using the supplied batch files or your changes won’t take effect.

Now we can write some code. I created my files using UltraEdit. I’m sure there are some great PHP IDEs out there but I wasn’t familiar with any. To run a php program, you simply create a file in the ..\udrive\www directory ending with the .php extension and then point your browser to http://localhost:8080/file.php. Note that you can omit the port in the URL if you are using the standard port setting of 80. In IE 7, I needed to hit ctrl-F5 to rerun my page after I had saved changes.

If you’ve looked at some other resources, keep in mind that we are using the built-in PHP soap support in PHP 5, not the older, uglier things like NuSoap, etc. The latest, built-in web service support works from wsdls like all the other modern web service platforms. However, PHP is still PHP, so even though creating the client is now easy, figuring out what to pass is not. Here’s the code to create the client object from a wsdl (if your wsdl URL starts with HTTPS, see below):

<?php
$client = new SoapClient("http://www.thomas-bayer.com/names-service/soap?wsdl");
&#91;/sourcecode&#93;

Note the first line.  I will be omitting it from all the rest of the code samples.

Here we are working with a web method that in c# took just a plain, old string as the parameter.  I could invoke it in c# with: 

client.getNameInfo("John");

By analogy, I expected to do something similar in PHP:

&#91;sourcecode language='php'&#93;
$client = new SoapClient("http://www.thomas-bayer.com/names-service/soap?wsdl");
print $client->getNameInfo("John");

Imagine my dismay when I received this error message:

Fatal error: Uncaught SoapFault exception: [ns2:Server] java.lang.NullPointerException

I’m not even using Java here! What’s going on? This service must be implemented using Java. More importantly, you need some way to see what the problems really are and what messages were sent and/or received. Here’s where reading this post really pays off:

<?php

$client = new SoapClient(
	"http://www.thomas-bayer.com/names-service/soap?wsdl"
	,array("trace" => 1, "exceptions" => 0)
);

$functions = $client->__getFunctions();
print_r($functions);
$types = $client->__getTypes();
print_r($types);

$response = $client->getNameInfo("John");
print "<br><br>";
print_r($response);

print "<br><br><pre>\n";
	  print "Request :\n".htmlspecialchars($client->__getLastRequest()) ."\n";
	  print "Response:\n".htmlspecialchars($client->__getLastResponse())."\n";
	  print "</pre>";

Note the trace and exceptions options in line 4. In combination with lines 16-19, they allow you to see what XML was actually sent and received even if you encounter errors or Soap faults. This is very helpful in debugging. We also want to see how PHP interpreted the WSDL which is what lines 7 – 10 display. Without this information, it’s very difficult to construct the object that you will pass to the web method.

Here’s the output of the program above:

Array ( [0] => getCountriesResponse getCountries(getCountries $parameters) 
[1] => getNamesInCountryResponse getNamesInCountry(getNamesInCountry $parameters) 
[2] => getNameInfoResponse getNameInfo(getNameInfo $parameters) ) 
Array ( [0] => struct getNameInfo { string name; } 
[1] => struct getNameInfoResponse { nameInfo nameinfo; } 
[2] => struct nameInfo { string name; string gender; boolean male; boolean female; countries countries; } 
[3] => struct countries { string country; } 
[4] => struct getNamesInCountry { string country; } 
[5] => struct getNamesInCountryResponse { string name; } 
[6] => struct getCountries { } 
[7] => struct getCountriesResponse { string country; } ) 

Request :
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://namesservice.thomas_bayer.com/"><SOAP-ENV:Body><ns1:getNameInfo/></SOAP-ENV:Body></SOAP-ENV:Envelope>

Response:
<?xml version="1.0" ?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><ns2:Fault xmlns:ns2="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns3="http://www.w3.org/2003/05/soap-envelope"><faultcode>ns2:Server</faultcode><faultstring>java.lang.NullPointerException</faultstring></ns2:Fault></S:Body></S:Envelope>

If you look at the request, you’ll see we are sending an empty getNameInfo element and the web service assumes that we sent a string. Hence, the null reference exception. Looking at line 5 above, we can see that to fix this, we need to send an object with a string member so we change:

$response = $client->getNameInfo("John");

To:

$params->name = "John";
$response = $client->getNameInfo($params);

Now our call succeeds. This publicly visible web service had a simple signature. Suppose you were working (as I was) with something more complex. In my case, I had two complex parameters being passed, the second of which included an array of a complex type. Here’s what my call ended up looking like:

$user->GroupId = "TestGroup";
$user->Username = "TestUser";

$req->CompanyID = "12345";
$req->Amount = 155.20;
$req->UserDefinedFields[0]->FieldName = "Name";
$req->UserDefinedFields[0]->FieldValue = "Jane Smith";
$req->UserDefinedFields[1]->FieldName = "TrackingCode";
$req->UserDefinedFields[1]->FieldValue = "999";

$params->user = $user;
$params->purchaseReq = $req;

$response = $client->SubmitPurchaseReq($params);

If you need access URLs using SSL, then you will need to download, deploy and enable php_openssl.dll (see steps above for php_soap.dll). In addition, you will need to copy libeay32.dll to your windows\system32 directory. Libeay32.dll is in the same zip but the parent directory of the one that php_openssl.dll is in.

Hopefully, your first PHP web services experience will go more smoothly than mine.

C# Generic Covariance

Intuitively, an immutable, generic collection of a subclass should be covariant with a collection of the superclass. Here’s an example:

interface IWidget
class Widget : IWidget
interface IWidgetCollection : IEnumerable<IWidget>
class WidgetCollection : List<Widget>

As I think most experienced developers will recognize, this is not a contrived example (aside from the “Widget” part) and is something that would be extremely useful. I am repeatedly surprised when I receive the compiler error that you cannot convert WidgetCollection to IEnumerable<IWidget>. List implements IEnumerable so WidgetCollection implements IEnumerable<Widget>. Thus, the crux of the matter is: should IEnumerable<Widget> be covariant with IEnumerable<IWidget> given that Widget : IWidget? I say yes and I’ve yet to see a good reason as to why not.

There’s actually a very good reason why List<Widget> is not covariant with List<IWidget>. In a nutshell, covariance of non-immutable collections would potentially allow insertion of a different subclass into a collection of another subclass through upcasting. See this for full-blown details. However, this scenario does not apply to IEnumerable nor immutable collections.

I’ve encountered this very sticky problem twice in the real world. Most recently, I was trying to use generic collections of an interface in combination with generic collections of classes that implemented the interface to implement the strategy pattern in way that didn’t require downcasting.

List<A> aic = _retreiver.Get(max);
foreach (A ai in aic)
{
    _updater.UpdateRemote(ai);
    _updater.UpdateLocal(ai);
}
_updateCompletionRecorder.RecordCompletion(aic);

_retreiver, _updater, and _updateCompletionRecorder are strategies and A is a generic type variable. In terms of the intro example, I needed _updateCompletionRecorder to be able to work with Widgets instead of IWidgets without downcasting in the implementation.

In addition, the project I was working on uses remoting. So I needed to declare non-generic types (which I feel is good practice anyway) for my collections since you cannot serialize a directly generic collection, EG WidgetCollection (serializable) instead of List<Widget> (not serializable). In this case, I was utlimately able to leave List<> in to solve the problem since I ended up not needing to cross a remoting boundary in this case. However, I wasted quite a bit of time and ended up with an inferior design, IMO.

Everthing would have been much better if IEnumerable<Widget> had been covariant with IEnumerable<IWidget>.

Wireless Hell

I’m sure being a doctor is even more annoying, but as the tech geek, I am always being called to solve all my friend’s and family’s techical problems. A buddy of mine recently changed jobs and had a new laptop. He’d had wireless working flawlessly at his house with the old laptop (with my help, of course) for the past year or so. Now, he has a new laptop and he expected to be able to just type in the passkey and things would work. No such luck.

Why does every wireless hardware vendor think the world wants to use thier, custom, oh-so-much-better wireless management module (or whatever the hell it is). Even if theirs worked perfectly (which was not the case), is it really any better than the one that comes with Windows? Even if it was better, I don’t want to learn the ideosyncrancies of their implementation. If things are working, the user hopefully will never see that piece of software again. So when they do, why the hell should it look and feel completely different for every damned wireless vendor? Wirelss isn’t new anymore! I didn’t have to use a different, vendor specific window (at least not for the last 10 years) to set up my wired lan cards. Why the hell do these vendors think I want to for my wireless card?

Resharper 3.x is Buggy as Hell

I was a huge resharper fan…until I installed 3.x. I have experienced nothing but bugs. The little bug/feedback box pops up 5 times a day on average. I have to delete all the resharper files to get the arrow keys working in one of my projects. I’ve submitted numerous bug reports and even gone to the trouble of providing screenshots and bug-demoing projects. None of them have been addressed yet.

I have read a lot of positive posts about 3.x but I really find it hard to believe that I’m the only one out there having these problems. So, as unpopular is it may make me, I’m making this post giving Resharper 3.x a big thumbs down.

P.S. I recently installed 3.0.3 build 3.0.520.30 and things are working much better.