I will now show you how to set up an alternative, generic proxy with PHP. This proxy lets you to tap into any SOAP web service directly from Flash by using PHP as a bridge. I will use the MillionaireQuiz web service written by Konrad Wulf. It accesses a database of multiple-choice questions similar to those typical of the "Who Wants To Be A Millionaire" television show. Check out a live example.
The generic proxy API consists of two ActionScript classes and two PHP files. The files are as follows:
LoadVars object to send and receive web service data to and from GenericProxy.php. It uses the Serializer class to serialize the data before sending it out and to unserialize it after receiving it from PHP. You should save this file in one of your Classes directories, such as C:\Documents and Settings\{your username}\Local Settings\Application Data\Macromedia\Flash MX 2004\en\Configuration\Classes\com\ws\.Open a text editor of your choice to write the following scripts:
GenericProxy.php:
<?php
// Grab values from REQUEST and unserialize
$wsdl = unserialize(urldecode($_REQUEST['wsdl']));
$methodName = unserialize(urldecode($_REQUEST['methodName']));
$paramArray = array_reverse(unserialize(urldecode(stripslashes($_REQUEST['paramArray']))));
// Include nusoap.php file.
require_once('nusoap.php');
// Define new object via nusoap.php and specify location of WSDL
$soapclient = new soapclient($wsdl,'wsdl');
// Set timeouts, nusoap default is 30
$soapclient->timeout = 500;
$soapclient->response_timeout = 500;
// Call the web service operation/method and accept the result
$SOAPResult = $soapclient->call($methodName,$paramArray);
// Serialize the result before sending back to Flash
$serializedResult = utf8_encode(serialize($SOAPResult));
// Send result back to Flash
print "&myResult=" . urlencode($serializedResult);
?>
Put GenericProxy.php on the server in the same directory as nusoap.php.
GenericProxy.as:
// import the Serializer class
import it.sephiroth.Serializer;
class com.ws.GenericProxy {
// object to un/serialize data before sending/receiving to/from PHP
private var serial:Serializer;
// LoadVars object to send and receive the data
private var lv:LoadVars;
// WSDL for the web service
public var wsdl:String;
// unserialized result sent back from web service via PHP
public var myResult:Object;
// function that is called when result comes back from PHP;
// defined by user
public var onResult:Function;
// constructor
function GenericProxy(wsdlURI) {
wsdl = wsdlURI;
}
// method that calls the web service via PHP
public function makeMethodCall(methodName, paramArray) {
// define new Serializer object
serial = new Serializer();
// define the LoadVars object
lv = new LoadVars();
// prepare the variables that the LoadVars object will pass
// to GenericProxy.php by defining and serializing them
lv.wsdl = escape(serial.serialize(wsdl));
lv.methodName = escape(serial.serialize(methodName));
lv.paramArray = escape(serial.serialize(paramArray));
// create reference to class instance to be available
// inside the LoadVars object
lv.gp = this;
// function that is called when the LoadVars object
// receives data back from GenericProxy.php
lv.onLoad = function(success) {
if (success) {
// invoke onResult method and pass unserialized result to it
this.gp.onResult(this.gp.serial.unserialize(this.myResult));
} else {
// The data didn't load right.
// Note: this has nothing to do with the actual web service
trace("LoadVars failure");
}
};
// call GenericProxy.php to pass the LoadVars variables
// to it
lv.sendAndLoad("GenericProxy.php", lv, "POST");
}
}
Save this file at C:\Documents and Settings\{your username}\Local Settings\Application Data\Macromedia\Flash MX 2004\en\Configuration\Classes\com\ws\. Note that while doing local testing, you may need to replace the following statement:
lv.sendAndLoad("GenericProxy.php", lv, "POST");
with this one:
lv.sendAndLoad("http://localhost/GenericProxy.php", lv, "POST");
Now, create the FLA file that will use the GenericProxy class to connect to the MillionaireQuiz web service.
Label them new question and check answer and give them instance names of questionButton and checkButton.
Figure 1: MillionaireQuiz file showing layers and interface elements
stop() action on each of these frames. Leave the first frame blank and place graphics or text indicating that the user has answered the question correctly or incorrectly on the two others. Give this movie clip the instance name feedback.
// import the generic proxy class
import com.ws.GenericProxy;
// import the controls classes so we can add UI components
// at runtime
import mx.controls.*;
// define the location of the WSDL
wsdl = "http://java.rus.uni-stuttgart.de/quiz/quiz.wsdl";
// create a new GenericProxy object
var proxy:GenericProxy = new GenericProxy(wsdl);
// set global depth that will be incremented every time
// a new visual element is added dynamically
_global.MAXDEPTH = 5;
// create a reference to the timeline
var host:MovieClip = this;
// create an array with possible answers
var answerRefs:Array = ["A", "B", "C", "D"];
// set some spacing variables for laying out the content
var xSpacing:Number = 20;
var xSpot:Number = 20;
var ySpacing:Number = 50;
var ySpot:Number = 50;
// initiate two more variables
var answerCount:Number;
var questionRef:Object;
// initiate the app by getting the first question
getQuestion();
// what to do when the "new question" button is clicked
questionButton.onRelease = function() {
cleanUp();
getQuestion();
}
// what to do when the "check answer" button is clicked
checkButton.onRelease = function() {
checkAnswer();
}
// get a new question from the database
function getQuestion() {
// disable all UI elements
disable();
// make a method call to getRandomQuestion()
// via the proxy service object
proxy.makeMethodCall("getRandomQuestion");
// assign a method to the proxy's onResult
proxy.onResult = getQuestionResult;
}
// function that is called when web service result arrives
function getQuestionResult(WSResult:Object) {
answerCount= 0;
ySpot = 50;
statusText.text = "";
questionRef = WSResult;
// create a text field for the question
host.createTextField("q", MAXDEPTH++, xSpot, ySpot, 250, 50);
host.q.multiline = true;
host.q.wordWrap = true;
host.q.text = questionRef.question;
ySpot += 50;
for (var i in questionRef) {
if (i.substr(0, 6) == "answer") {
// create a radio button for each multiple-choice answer
host.createClassObject(RadioButton, "rb"+answerCount, MAXDEPTH++);
var rb = host["rb"+answerCount];
rb.groupName = "answerGroup";
if (answerCount == 0) {
rb.selected = true;
}
rb._x = xSpot;
rb._y = ySpot+3;
// create a text field for each answer
host.createTextField("answer"+answerCount, MAXDEPTH++, xSpot+xSpacing, ySpot, 200, 20);
var a = host["answer"+answerCount];
a.text = questionRef["answer"+answerRefs[answerCount]];
// create a horizontal rule after each answer
host.attachMovie("hr", "hr"+answerCount, MAXDEPTH++);
var hr = host["hr"+answerCount];
hr._x = xSpot;
hr._y = a._y+a._height+10;
ySpot += ySpacing;
answerCount++;
}
}
// re-enable UI elements
enable();
}
// check the answer the user selected
function checkAnswer() {
disable();
// prepare the parameters to be sent to web service
var id = questionRef.id;
var guessedAnswer = answerRefs[host.answerGroup.selection._name.substr(2, 1)];
parameters = [id, guessedAnswer];
// make the web service call to checkCorrectAnswerById(),
// and pass the parameters
proxy.makeMethodCall("checkCorrectAnswerById", parameters);
// assign the onResult method for the proxy
proxy.onResult = checkAnswerResult;
}
// function that is called when result comes back from call
// to checkCorrectAnswerById()
function checkAnswerResult(WSResult:String) {
statusText.text = "";
// decide whether to display "correct" or "incorrect" visual
if (WSResult == true) {
feedback.gotoAndStop("correct");
} else {
feedback.gotoAndStop("incorrect");
}
enable();
}
// cleans up question and answers when a new one is requested
function cleanUp() {
for (var i = 0; i < answerCount; i++) {
host["rb" + i].removeMovieClip();
host["answer" + i].removeTextField();
host["hr" + i].removeMovieClip();
}
host.q.removeTextField();
}
// two functions to en/disable UI elements
function enable() {
checkButton.enabled = true;
questionButton.enabled = true;
}
function disable() {
checkButton.enabled = false;
questionButton.enabled = false;
lilMessage = "<talking to web service, please wait>";
statusText.text = lilMessage.toUpperCase();
feedback.gotoAndStop(1);
}
When you test movie now, you will first see a message indicating that you are connecting to the web service. After a moment, you will see the first question and answer set dynamically constructed on the screen by createClassObject, createTextField, and attachMovie.
The important lines of code are the following:
wsdl = "http://java.rus.uni-stuttgart.de/quiz/quiz.wsdl";
var proxy:GenericProxy = new GenericProxy(wsdl);
proxy.makeMethodCall("getRandomQuestion");
proxy.onResult = getQuestionResult;
Here you establish an instance of the GenericProxy ActionScript class by passing to its constructor the WSDL of the web service you would like to connect to. You then use its makeMethodCall() method to call the web service's getRandomQuestion() operation. You also define the proxy's onResult method to determine what should happen when the result comes back from the web service. Remember that the GenericProxy ActionScript class and its PHP partner GenericProxy.php handle all the rest, such as passing the parameters to nusoap.php and serializing and deserializing the data sent to and from PHP. The generic proxy you have created lets you make any web service call in just four lines of code.
PHP is of course not the only way to build a generic web service proxy. You can create one in any server-side language. To build one in ASP.NET, Chris Peiris' article, Creating a Proxy Web Service Object, is a good place to start. If, on the other hand, you prefer Java, Axis might be an option to explore. Whichever programming language you choose, you will be well-advised to build on an existing server-side object that can connect to a web service. In my example, nusoap.php worked great. You could write something similar yourself, but it would be a lot more work.
I hope this has given you some insight into the wonderful world of web services in Flash MX Professional 2004. Now go show those web services who's boss!