AMFPHP in Flash CS3 with AS3 – jQuery.post style

The other day I was asked to demonstrate how to setup AMFPHP in Flash. As I’ve already done it many times in Flash 8 with little or no problems I felt relaxed about it. The machine I was to demonstrate on only had Flash CS3, no problem I thought and fired up an Actionscript 2 project. The extra components you are required to install in Flash 8 are probably already integrated by now I thought and copy pasted code from an older project, hit ctrl-enter and got a debug window complaining about a lot of missing classes. Apparently you have to copy that stuff from somewhere in the Flash 8 program dir tree and put in the corresponding place in the Flash CS3 dir tree. We didn’t have any Flash 8 installed though…


So I started a new AS3 project instead and started googling my ass off for a quick solution (the designers I were demoing to started getting restless), which I found in the form of Oscar Trelles‘ nice AS3 Flash remoting example. I followed the tutorial but no it didn’t work. The staging server had .htaccess with password protection that ruined things this time. I had to give up, I told them I would get back to them and here I am!

Initially I thought that this stuff would have advanced since the AS2 days, but no. Quite the contrary, I couldn’t find an equivalent to the Net Debugger, wtf was happening? No debugger? That would make the whole AS3 proposition completely useless. Shocking considering that Flex and AMFPHP is such a smooth ride. I started to browse the comments on Oscar’s tutorial and found a link to the home of Josh Strike and his Strike Remoting which worked like a charm, awesome! The Net Debugger was back, what a relief.

In the readme of Strike Remoting I also found the best explanation so far as to why remoting in Flash CS3 sucks per default:

Adobe conveniently failed to include AS3 remoting components in Flash CS3. Apparently they don’t consider Flash a viable platform for information apps. Their attitude on it is somewhere between “go buy Flex” and “go **** yourself”. Not that the original NetConnectionDebugger worked real well anyway; but that’s another story…

Normal interfaces for databases are all fine with this I suppose but what about all the “irregular” stuff you might want to do like for instance games that need a db connection? Damn it Adobe!

Initially I tried to use the Strike code with various flags turned off in order to parse the return data from PHP but it didn’t work out very well. No matter how I tried I couldn’t seem to get rid of various errors pertaining to the GUI in the Example.fla file. The solution was to simply grab the juiciest parts and go at it myself.

The way I’ve done this is to download AMFPHP (I used the amfphp-1.9.beta.20070513.zip but the 2008 version should work just as good or better). I also extracted Strike Remoting into the AMFPHP directory (it’s C:\wamp\www\amfphp for me), it looks like this:

file_structure.jpg

Let’s start with test.php placed in the services directory:

<?php
class Test{
  function test($args){
    return array(
      "arg1" => $args['arg1'], 
      "arg2" => $args['arg2'], 
      "apple" => "fruit", 
      "purple" => "color"
    );
  }
}
?>

The $args argument is an AS3 object which in our case looks like this: {arg1:’arg1′, arg2:’arg2′}. Yeah I know, not very imaginative, whatever. After that it was time to try out Strike Remoting (SR), I opened Example.as in SEPY and changed two lines:

. . .
var p:Remoting = Remoting.getInstance(root,"http://localhost/amfphp/gateway.php");
. . .
var testCall:PHPCall = new PHPCall(Remoting.PHP,this.gotTest,"Test.test", {arg1:"arg1", arg2:"arg2"});
. . .

Opening Example.fla in Flash and doing ctrl-enter resulted in the following:

strike_remoting.png

It was time to start working on my own stuff, I decided to keep Oscar’s RemotingService.as:

package{
	import flash.net.NetConnection;
	import flash.net.ObjectEncoding;
	public class RemotingService extends NetConnection{
		function RemotingService(url:String){
			objectEncoding = ObjectEncoding.AMF0;
			connect(url);
		}
	}
}

Notice the use of AMF0, that’s because of this ominous comment in the source of SR (in PHPAccess.as, line 50):

/*USING AMF0 IS RECOMMENDED AT LEAST UNTIL AMFPHP 2.0 SETTLES ISSUES WITH RECORDSETS & ARRAYS IN THE SAME CALL!!!*/

The main script is CallPHP.as and looks like this:

package{
	import flash.system.Security;
	import flash.events.NetStatusEvent;
	import flash.net.Responder;
	
	public class CallPHP{
		
		public var rs:RemotingService;
		public var r:Object;
		public var resp:Responder;
		public var callBack:Function;
		
		function CallPHP(url:String){
			Security.allowDomain("localhost");
			this.r = new Object();
			this.rs = new RemotingService(url);
			this.resp = new Responder(this.onResult, this.onFault);
		}
		
		public function call(classMethod:String, args:Object, callBack:Function){
			this.callBack = callBack;
			this.rs.call(classMethod, this.resp, args);
		}
		//From http://www.joshstrike.com/ START
		public function convRec(r:Object):Array {
			var rsArr:Array = new Array();
			var colCount:Number = r.serverInfo.columnNames.length;
			for (var row:Number = 0; row < r.serverInfo.initialData.length; row++) {
				rsArr[row] = new Array();
				for (var colIndex:* in r.serverInfo.columnNames) {
					rsArr[row][r.serverInfo.columnNames[colIndex]] = r.serverInfo.initialData[row][colIndex];
				}
			}
			return rsArr;
		}
		
		public dynamic function onResult(re:Object):void{
			var resultType = "null";
			if (!re) {
				resultType = "false";
				var outFalse:Boolean = false;
			} else if (re is Number) {
				resultType = "Number";
				var outN:Number = Number(re);
			} else if (re is String) {
				resultType = "String";
				var outS:String = String(re);
			} else {
				var outA:Object = new Object();
				if (!("serverInfo" in re)) {
					resultType = "Array";
					outA = re;
					for (var i:* in re) {
						if (re[i]) {
							if ("serverInfo" in re[i]) {
								var z:Array = this.convRec(re[i]);
								re[i] = z;
							}
						}
					}
				} else {
					resultType = "RecordSet";
					var rsArr:Array = new Array();
					var colCount:Number = re.serverInfo.columnNames.length;
					for (var row:Number = 0; row < re.serverInfo.initialData.length; row++) {
						rsArr[row] = new Array();
						for (var colIndex:* in re.serverInfo.columnNames) {
							rsArr[row][re.serverInfo.columnNames[colIndex]] = re.serverInfo.initialData[row][colIndex];
						}
					}
					outA = rsArr;
				}
			}
			switch (resultType) {
				case "false": 
					this.r.result = outFalse;
					break;
				case "Number":
					this.r.result = outN;
					break;
				case "String":
					this.r.result = outS;
					break;
				case "Array":
					this.r.result = outA;
					break;
				case "RecordSet":
					this.r.result = outA;
					break;
				default: 
					this.r.result = null;
					break;
			}
                        //From http://www.joshstrike.com/ END
			this.r.type = resultType;
			this.callBack(this.r.result);
		}
		
		public dynamic function onFault(fault:Object):void{ trace(fault.result); }
	}
}

The convRec method and everything but the two last lines of onResult() is Josh’s code, raw copy paste from SR combined with deleting some debug related lines. Notice the callBack argument in the call method in the form of a function? Passing that function (yes I love this aspect of Javascript and Actionscript) will let us retrieve the asynchronous response from the PHP in a convenient way in amfphptest_as3.fla which only has this code in its first frame:

var amf:CallPHP = new CallPHP("http://localhost/amfphp/gateway.php");
amf.call("Test.test", {arg1:'arg1', arg2:'arg2'}, function(fromPhp){
	trace(fromPhp['purple']);														
});

Output: “color”. If this won’t make you think “that’s very similar to $.post” you might want to check it out, it’s Ajax made as simple as it gets. We can sigh in relief and get on with it in the post AS2 world.

Related Posts

Tags: , , , , ,