Accessibility

Table of Contents

Getting a Handle on Web Services in Macromedia Flash MX Professional 2004

Building a Generic Web Service Proxy in PHP

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:

  • nusoap.php: (This file was written by Dietrich Ayala; download it from his website if you didn't do so when reading the Requirements section.) It handles the SOAP request and response. Put this file in a directory on the server.
  • GenericProxy.php: You will write this as you follow the steps below. Flash calls this file and accepts as arguments the WSDL, the name of the web service operation, and an array of parameters to be passed to that operation. It then calls nusoap.php to perform the SOAP wrapping of the request and response. You must save this file on the server in the same directory as nusoap.php.
  • Serializer.as: (This file was written by Alessandro Sephiroth: download it from his website if you didn't do so in the Requirements section.) When you send data back and forth between Flash and PHP, you can lose complex data such as arrays or objects. The Serializer class lets you serialize and unserialize complex data, so you can retain its structure between Flash and PHP, and vice versa. Save the ActionScript (AS) file under C:\Documents and Settings\{your username}\Local Settings\Application Data\Macromedia\Flash MX 2004\en\Configuration\Classes\it\sephiroth\.
  • GenericProxy.as: You will write this code in the steps below. This AS file creates a 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.

  1. Open Flash MX Professional 2004 and create a new Flash document. Name it MillionaireQuiz and save it.
  2. Create two layers. Name one code and the other one stuff. You will put code on the code layer and visual elements on the stuff layer.
  3. Drag two Button components from the Components Panel to the Stage.
  4. Label them new question and check answer and give them instance names of questionButton and checkButton.

    MillionaireQuiz file showing layers and interface elements

    Figure 1: MillionaireQuiz file showing layers and interface elements

  5. Create a dynamic status text field that will tell the user when data is loading. Give this text field an instance name of statusText.
  6. Create a movie clip and name it feedback. Label three of its frames default, correct, and incorrect, and place a 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.
  7. Drag a RadioButton component from the Components panel to the Stage and then delete it. This places it in the library. You will add RadioButtons with ActionScript; the symbol needs to be present only in the library.
  8. In the Library panel, create a movie clip with a horizontal line in it. Give it a linkage id of hr (for horizontal rule).
  9. Add the following code to the first frame of the code layer. I know it looks like a lot, but comments make up most of it, I promise!
// 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!