/PHP/ Make your own Soap
05/01/2007 | Filed under Develop > PHP

No, this isn’t a do-it-yourself version of Fight Club. Our regular PHP expert, Paul Hudson, walks you through how to create your own API for others to use
I’m sure you’ve all noticed that this magazine enjoyed a comprehensive redesign a few issues ago, and we adopted the new slogan ‘develop/ discover/design’. Well, I’m a man with so little sense of appearance that I dress in the dark, so I can’t teach you anything about design. But in the last two issues, I hope you’ve joined me to discover how to take advantage of the excellent Google, Fl ickr and Delicious APIs to get extra content for your site.
This issue, we’re going to look at my favourite member of the .net trinity: ‘Develop’. When working with Google, we had to use Soap and WSDL, two of the simplest methods of defining a strict API for others to build on. We already used the excellent SoapClient class, but there’s an equally excellent SoapServer class that also works with WSDL, and we can combine them to share our content with the world, all thanks to PHP. Of course, our code will serve up content to everyone who requests it regardless of whether or not they’re using PHP, but our server will be written using PHP.
The advantage of using WSDL is that we define our API in a clear, crossplatform way using an open XML standard, rather than trying to write a few thousand lines of PHP then have to write all the documentation to go along with it. WSDL provides the basis for our code, but is also self-documenting, which is perfect for anyone who doesn’t enjoy writing documentation (that’s all of us). The fact that our WSDL provides the base for our client and server code is an added bonus that means you only need to change one file to keep your API up to date.
Working out the WSDL
The very first step in creating an API is to decide what you actually want to achieve. This is usually done through use case diagrams, which are designed to compile a list of required inputs and outputs for a process. I don’t have the space within these pages to open a software engineering school, so I’m going to keep the requirements quite simple: I’m going to serve up puzzles. To do this, the API will define a getPuzzle() function that sends a request off to the server, which then picks a puzzle at random and sends it back. To make things a little more difficult, you’re going to let people specify what level of difficulty they want, with level 1 being the easiest and level 3 being the hardest.
Now, a word of warning: when working with WSDL, creating the first working code is always the most difficult. Your basic WSDL script is just shy of 40 lines of XML, and understanding it isn’t as easy as you may think. The actual PHP to drive the whole affair is a cinch in comparison, but you need to write the WSDL first.
To make it more readable, it’s good practice to break the WSDL into two parts. Save the whole file as ‘dotnet1.wsdl’ and make sure it’s in your public HTML directory. Here’s the first part:
<?xml version=”1.0”?>
<definitions name=”NetPuzzle” targetNamespace=”urn:NetPuzzle” xmlns:
typens=”urn:NetPuzzle” xmlns:xsd=”http://www.w3.org/2001/XMLSchema” xmlns:
soap=”http://schemas.xmlsoap.org/wsdl/soap/” xmlns:soapenc=”http://schemas.
xmlsoap.org/soap/encoding/” xmlns:wsdl=”http://schemas.xmlsoap.org/wsdl/”
xmlns=”http://schemas.xmlsoap.org/wsdl/”>
<message name=”getPuzzle”>
<part name=”difficulty” type=”xsd:int” />
</message>
<message name=”getPuzzleResponse”>
<part name=”return” type=”string” />
</message>
<portType name=”NetPuzzlePort”>
<operation name=”getPuzzle”>
<input message=”typens:getPuzzle” />
<output message=”typens:getPuzzleResponse” />
</operation>
</portType>
The opening two lines are standard XML fare, though you’ll notice that you need to suck in a number of XML namespaces, as a variety of technologies are being used. The next two sections define the ‘messages’ and ‘operations’ we wish to perform. Each message is either something that’s sent to the server or something received back, and it’s here where you define exactly what the messages should contain. For your basic needs, I’ve said that the getPuzzle message must contain a number (‘int’ is short for ‘integer’, meaning ‘whole number’) that is the difficult rating, and that the puzzle response should be in the form of a string.
Beneath the messages is the portType element, but don’t worry about this just yet. For now, care only about its contents, which state that there’s an operation (a function, in PHP terminology) called getPuzzle(), which is called through the getPuzzle() message and returns a getPuzzleResponse() message. It’s operations that tie messages together into functions, which is why the name of the operation, getPuzzle, gets mapped to the getPuzzle() PHP function. Here’s the other half of the WSDL file:
<binding name=”NetPuzzleBinding” type=”typens:NetPuzzlePort”>
<soap:binding style=”rpc” transport=”http://schemas.xmlsoap.org/soap/http” />
<operation name=”getPuzzle”>
<soap:operation soapAction=”urn:NetPuzzleAction” />
<input>
<soap:body use=”encoded” namespace=”urn:NetPuzzle”
encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/” />
</input>
<output>
<soap:body use=”encoded” namespace=”urn:NetPuzzle”
encodingStyle=”http://schemas.xmlsoap.org/soap/encoding/” />
</output>
</operation>
</binding>
<service name=”NetPuzzleService”>
<port name=”NetPuzzlePort” binding=”typens:NetPuzzleBinding”>
<soap:address location=”http://yourserver.com/server1.php” />
</port>
</service>
The whole <binding> element is required, yet can largely be ignored. What it does is say that the getPuzzle operation uses Soap for both its input and output, but in true XML style, it takes more than 10 lines of code to say this. If you want to add your own operations in future, just copy and paste this wholesale. You won’t actually need to change anything other than the name of the operation being used.
The final element, <service>, is very important, as it defines where the service resides on the internet. The portType element mentioned earlier references this before describing the operations that are available. In practice, this means you can have different operations pointing to different scripts if you want to.
You’ve now defined your parameters (or ‘messages’, in Soap-speak), your function (‘operation’), your protocol (‘binding’) and your URL (‘service’), which means you have all you need to make a complete function call. This means you can leave XML behind for now and switch to PHP.
Client and server
After so much XML, this code will probably make you laugh:
<?php
$soap = new SoapClient(“dotnet1.wsdl”);
echo $soap->getPuzzle(“1”);
?>
Two lines of PHP is all it takes to load the API, make the function call and print the results. Save this as ‘1.php’ in the same directory as your WSDL file. This code requests an easy puzzle from the server, so you need to program the final component of the API: the server. This needs to accept the Soap request, handle the getPuzzle() function, then output the puzzle for the client. PHP does most of the hard work for you, which makes the code remarkably short:
<?php
function getPuzzle($difficulty) {
$puzzles[1][] = “What is the best web mag in the world?”;
$puzzles[2][] = “What is the air-speed velocity of an unladen swallow?”;
$puzzles[3][] = “What is the meaning of life?”;
$randpuz = array_rand($puzzles[$difficulty]);
return $puzzles[$difficulty][$randpuz];
}
$server = new SoapServer(“dotnet1.wsdl”);
$server->addFunction(“getPuzzle”);
$server->handle();
?>
The bulk of this script is the getPuzzle() function, which takes a parameter to store the difficulty that was requested. This function stores your puzzles in the $puzzles array, but if you want to implement this seriously, you should switch to an SQL database with a query similar to this one:
SELECT Question FROM puzzles WHERE Difficulty = $difficulty ORDER BY rand()
LIMIT 1;
The function returns a value by plucking a question randomly from the $puzzles array at the correct difficulty level. Of course, each difficulty level has just one question, so the same question is always returned! The three lines at the end of the script comprise the Soap magic that hooks up our PHP function to the Soap getPuzzle() call. You need to call addFunction() for each Soap/PHP bridge you want to register. When you’re done, you simply need to call the handle() method to have PHP parse the Soap input, route it to the appropriate functions, then send the value back to the client.
I know you’re probably eager to run the code, but you first need to disable PHP’s WSDL cache. You’ve already seen that the WSDL required to make a single function call is very long, so to avoid having to parse and validate it every single time a client comes along, PHP caches the WSDL in its parsed state. While you’re still at the development stage, this causes problems. Any changes made to the WSDL file won’t be spotted by PHP, because it will still be using its cached version. The solution is to temporarily disable PHP’s caching system, which can be achieved by changing your ‘php.ini’ file: look for the ‘soap.wsdl_cache_enabled’ line and change it to ‘0’.
With this change implemented (restart Apache if you’re running PHP as a module), load your ‘1.php’ script. You should see ‘What is the best web mag in the world?’, which shows that our client has received a message back from the server.
Working
Before we finish, there are three final points I’d like to make to help you keep on top of your WSDL. First, ensure your API is stable. If people get used to calling your getPuzzle() method, then don’t change it. Don’t add new parameters, don’t change the data type of existing parameters and certainly don’t drastically alter what the function actually does. That doesn’t mean you can’t rewrite your PHP code to have getPuzzle() do the same thing in a smarter way, but if you really must change the API (ie, add a parameter or whatever), leave the old one alone and make a new one called getPuzzle2(). Second, don’t forget to notify people when your WSDL file changes, so that they know to download the newest version to take advantage of the newest features. Finally, although PHP doesn’t distinguish between ints and strings, other languages do, so choose them wisely.
Hopefully, you’ve seen that – at least once you’re past the initial pain of writing WSDL – using Soap is easy. PHP takes away so much of the pain of handling data transfer, which means you can focus on getting your functions correct on the client and server sides. As you progress more with WSDL, you’ll learn to appreciate its verbosity, because PHP is able to do some basic type-checking on your data to ensure you’re sending the right content through Soap.
Bookmark with:
Comments
Alexis / 05/02/2007 / 19:36
dont have a comment
Tapan Shah / 22/02/2007 / 07:15 / http://www.tapan.in
I cant find the tutorial for SOAP client class for google, flickr and delicious.. can you email me the link for that..
Heather / 06/03/2007 / 21:47
I just tried your tutorial - thanks for it being so well written and easy to understand.
Unfortunately, when I ran the 1.php I got an error. :-(
Fatal error: Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: Couldn't load from '/var/www/localhost/htdocs/enudge/dotnet1.wsdl' in /var/www/localhost/htdocs/enudge/1.php:2 Stack trace: #0 /var/www/localhost/htdocs/enudge/1.php(2): SoapClient->__construct('dotnet1.wsdl') #1 {main} thrown in /var/www/localhost/htdocs/enudge/1.php on line 2
Any ideas?
Heather / 07/03/2007 / 06:29
I managed to get the Client to commence operations, but now I am getting this error straight out of the box (so to speak):
Fatal error: Uncaught SoapFault exception: [HTTP] Client Error in /var/www/localhost/htdocs/beta/1.php:3 Stack trace: #0 [internal function]: SoapClient->__doRequest('<?xml version="...', 'http://beta.enu...';, 'urn:NetPuzzleAc...', 1) #1 [internal function]: SoapClient->__call('getPuzzle', Array) #2 /var/www/localhost/htdocs/beta/1.php(3): SoapClient->getPuzzle('1') #3 {main} thrown in /var/www/localhost/htdocs/beta/1.php on line 3
Don't know if you can help!
Thanks,Heather
John / 16/04/2007 / 06:01
The exmaple does not close off the WSDL properly. You need to add:
</definitions
at the end.
And watch out for the funny " marks, be sure to change them to the one on the keyboard.
Other than that, Thank's for the tutorial, I found it extremely helpful.
Arkady / 20/04/2007 / 12:52
The example works fine when changed as John adviced. (That is, complete the wsdl with </definitions> and replace what is seen as quotes with real quotes). Thanks!
Fahad / 10/07/2007 / 09:38
Cant run it getting following error:
Fatal error: Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: Couldn't load from 'http://localhost/dotNet.wsdl'; in C:\wamp\www\client.php:2 Stack trace: #0 C:\wamp\www\client.php(2): SoapClient->SoapClient('http://localhos...';) #1 {main} thrown in C:\wamp\www\client.php on line 2
edwin / 12/07/2007 / 10:36
I got the same error as Fahad:
Fatal error: Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: Couldn't load from 'C:\wamp\www\test\php5_soap\dotnet1.wsdl' in C:\wamp\www\test\php5_soap\client.php:2 Stack trace: #0 C:\wamp\www\test\php5_soap\client.php(2): SoapClient->__construct('dotnet1.wsdl') #1 {main} thrown in C:\wamp\www\test\php5_soap\client.php on line 2
I already added the missing closing definitions tag at the end of the wsdl file and also replace all fake quotes with real onces. Also, the url in de wsdl file has been changed to my server.
The php_soap extension was uncommented in my php.ini, so that wasn't the cause either.
Does someone know the solution?
thanx!
Fahad / 17/07/2007 / 11:36
Edwin there is problem with wsdl file the tags like xmlns:typens shold be on same line like
xmlns:typens
not like
xmlns:
typens
I managed to remove this error but still i am getting this error
Fatal error: Uncaught SoapFault exception: [Client] looks like we got no XML document in C:\wamp\www\client.php:3 Stack trace: #0 [internal function]: SoapClient->__call('getPuzzle', Array) #1 C:\wamp\www\client.php(3): SoapClient->getPuzzle('1') #2 {main} thrown in C:\wamp\www\client.php on line 3
HyGy / 24/07/2007 / 13:51
I can't find any info about using mysql with more than one soap function calls, and BEGIN, COMMIT, ROLLBACK.
If I have 3 functions:
startTransaction();
{
conn=new mysql conn;
conn->BEGIN;
}
sendData();
endTransaction();
{
conn->ROLLBACK;
}
When the client call the startTransaction(); first, then send data, then at last the rollback. But when the first startTrans... function call ends, the mysql make an auto rollback.
So the new data what I got is appends the old data in the database. (The data is about 250MB).
Is there a solution to this problem?
HyGy
Martin / 09/08/2007 / 13:27
To all who are having trouble with the paths for their WSDL files, remember that it is assumed that the client and the server access the same file, ergo, use an http:// path for your WSDL files.
kailash / 15/02/2008 / 17:02
i just try the above example "getpuzzle" i have already make change but stilll i have problem with it
Fatal error: Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: Couldn't load from 'D:\Program Files\Apache\htdocs\new\dotnet2.wsdl' in D:\Program Files\Apache\htdocs\new\1.php:2 Stack trace: #0 D:\Program Files\Apache\htdocs\new\1.php(2): SoapClient->SoapClient('dotnet2.wsdl') #1 {main} thrown in D:\Program Files\Apache\htdocs\new\1.php on line 2
plz heip me
Tom / 26/05/2008 / 18:52 / http://mypuzzle.org/
a big lol to "Alexis" ;-)
Photoshop man / 15/06/2008 / 04:58 / http://www.tutomaker.com
So, dosn't work with me, I've the same error "stack trace #0" but with google api...
Ian / 18/09/2008 / 17:30
I have the same error mentioned by Heather. Anyone know what that error might suggest? I get this error on the function call. Do you think it is the function having problem or the new Soapclient construction that is the problem?
Thanks,
Ian
Damian / 05/10/2008 / 15:20 / http://www.usher.com.pl
If you have problems talking to SOAP payment gateway interfaces, here's a tip: <a href="http://www.usher.com.pl/how-to-workaround-php-soapclient-bug-when-connecting-over-ssl/">How to workaround PHP’s SOAPClient bug when connecting over SSL</a>
baia mare / 07/11/2008 / 21:57 / http://www.divinedesign.ro
doesen't work for me neither. I'll guess is bad luck :(
Joseph / 18/11/2008 / 04:14 / http://www.raprap.info
To Heather
Fatal error: Uncaught SoapFault exception: Error
I've got the same error, the solution was the address of the soep server
new to soap / 10/03/2009 / 11:34
Fatal error: Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: Couldn't load from '“dotnet1wsdl”' in C:\wamp\www\soap_wsdl\1.php:2 Stack trace: #0 C:\wamp\www\soap_wsdl\1.php(2): SoapClient->SoapClient('?dotnet1wsdl?') #1 {main} thrown in C:\wamp\www\soap_wsdl\1.php on line 2
cant find solution for this...
Seeking for help..
Thanx in advance...
Simon Cooper / 15/03/2009 / 16:33
To those who are getting the error:
SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: Couldn't load from...
Enabling the php_openssl in PHP removed the error for me. Note that I was not attempting this tutorial, so it may not work for you.
Running Vista, PHP 5.2.4 (part of WAMP5 Version 1.7.3)
HML / 15/06/2009 / 15:30
Try also replacing the double quotes in your all files when you copy and paste from browser..
nullp0inter / 25/06/2009 / 17:14
I just get a blank screen. I changed the code somewhat, so here is my server code:
<?php
function postCustomerData($email,$fname,$lname,$src_url,$sign_up_date,$ip,$add1,$add2,$city,$state,$zip,$country,$phone,$gender,$dob) {
$welcome = "Hello $fname!";
return $welcome;
}
$server = new SoapServer("api.wsdl");
$server->addFunction("postCustomerData");
$server->handle();
?>
and client data:
<?php
$soap = new SoapClient("api.wsdl");
echo $soap->postCustomerData("xxxxx@gmail.com","xxxxxx","xxxxxxxx","http://xxxxxxg.com","07/01/2009","23.345.678","146 Orchard St.","34 Orchard St.","xxxxxx","FL","43326","USA","5555555555","male","11/11/11");
?>
all i get is a blank screen when visiting the client
Hershey / 04/01/2010 / 09:37
WSDLSOAP-ERROR: Parsing WSDL: Couldn't load from 'powsdl' - error
Where po.wsdl is the wsdl file. Please if any one can let me know where i am goin wrong.
Thanks.
dennis / 10/01/2010 / 21:52
For people who didn't seem to read the whole tutorial. You might get the "couldnt load" error because u didn't name the wsdl file dotnet1.wsdl. Also I didn't read the comments so I had to find out myself that </definitions> was missing.
When I tried validating the wsdl file, it said something about using a wrong attribute in the return line "string". I changed it to "xsd:string", and it worked.
Also, if u copy paste from here, u should replace the " with the correct symbol because the copy pasted version is not the same " which is valid.



