Zend Amf and class mapping with Flash CS3 / AS3

Update: I just got a mail from Julien Iafrancesco:

the solution is to use the registerClassAlias(“aliasName”, className) function somewhere before calling the php service instead of using the Flex attribute [RemoteClass(alias=”aliasName”)]. (which is irrevelant in flash, I took it off and it still works. )
In PHP, you just need to setup the alias with the setClassMap method of Zend_Amf_Server or one of the 2 other methods found in the zendframework docs.

Thanks for the pointer Julien!

So I did just like he says, this is what my code looks like at the moment and it works:

In the first frame of my Flash:

registerClassAlias("User", User);
var amf:CallPHP = new CallPHP("http://localhost/eggheadz/gateway.php");
amf.call("User.find", {id:'1'}, function(result){
	var user:User = User(result);
	user.sayHello();
});

And the AS3 User class:

package{
    public class User{
        public var id:int;
        public var username:String;
        public var password:String;
        public var gold:int;
        
        public function User() {}
        
        public function sayHello(){
        	trace("Hi I'm "+this.username);
        }
    }
}

And the PHP User class:

class User extends Service{
    public $id;
    public $username;
    public $password;
    public $gold;
    
	/**
	 * @param object $id
	 * @return object
	 */
	public function find($id){
		return $this->getT()->findOne($id->id, false)->asObj($this);
	}
}

In the PHP Service class I added the following:

public function getASClassName(){
  return get_class($this);
}

Those are the only changes that were made to the code below. AsObj() above will simply take an associative array and map its keys and values to the variable names and values of the variables in $this which in this case is User of course. Simple population.


Will I use this new way instead of the homegrown solution below? The answer might be surprising: NO. The fact that registerClassAlias(“User”, User); requires that second User to be a class makes it too cumbersome. I would very much like to add that line to the CallPHP class and somehow get it to work simply from the fact that we’re passing “User.find” to it, but my AS knowledge is too crappy for me to understand how I can go from the string “User” to somehow get at the class User, either that or it’s really impossible.

Either way, at the moment the whole automatic mapping thing doesn’t make sense when you can accomplish the same thing by simply looping through an object and map explicitly in the base object. Just like I’m doing below. That way you don’t have to declare member variables in the PHP model for instance, it just feels more flexible.

Sure it’s probably slower but it’s all happening on the client side anyway so it’s not important in like 99.99% of all cases as far as I can see. I’m sticking with my own solution until someone can convince me otherwise 🙂

End update

I just tried out Zend Amf, which is a no brainer to get up and running with built in data types such as arrays and strings. But what about class mapping? Well I’m going to have to kind of disappoint you, I didn’t get it to work the “proper” way, ie in the built in fashion described in the manual.

Instead I’m mapping the variables explicitly in the Shockwave, I have no example of doing the reverse, ie sending an object from flash and populating. I’m however not going to use an ORM like Doctrine in this project so that won’t really be a problem. The main thing is that we can easily populate AS objects.

Let’s start with what we have in the first frame of the Flash:

var amf:CallPHP = new CallPHP("http://localhost/secret_project/gateway.php");

amf.call("User.find", {id:'1'}, function(result){
	var user:User = new User(result);
	user.sayHello();
});

As you can see, yet again the jQuery influences are there. However as opposed to the major hassle it was to get stuff up and running with AMFPHP in that earlier article, we will now get things up and running with virtually no effort, just a little bit of copy paste from the example in the manual and a few modifications to that:

package {
  import flash.display.MovieClip;
  import flash.events.*;
  import flash.net.*;
  import flash.utils.*;

  public class CallPHP extends MovieClip {
    private var connection:NetConnection;
    private var responder:Responder;
    public var callBack:Function;
    public var gateway:String;

    public function CallPHP(gateway:String) {
      responder = new Responder(onResult, onFault);
      connection = new NetConnection;
      connection.connect(gateway);
      this.gateway = gateway;
    }

	public function call(classMethod:String, args:Object, callBack:Function):void{
	  this.callBack = callBack;
	  connection.call(classMethod, responder, args);
	}

    private function onResult(result):void {
      this.callBack(result);
    }
    
    private function onFault(fault:Object):void {
      trace(String(fault.description));
    }
}

Again we pass a function that will act as the recipient so that we can access the result in that jQuery Ajax style way. As you can se gateway.php is being called, let’s check it out:

error_reporting(E_PARSE);
set_include_path('.' . PATH_SEPARATOR . dirname(__FILE__). '/models/' . PATH_SEPARATOR . dirname(__FILE__). '/../lib/' . PATH_SEPARATOR . get_include_path());
$GLOBALS['db_user'] = 'root';
$GLOBALS['db_pass'] = '';
$GLOBALS['db_name'] = 'eggheadz';
$GLOBALS['db_host'] = 'localhost';
require_once('fluent/fluent.php');

include_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();
require_once('smoc/Mysql.php');
require_once('Service.php');

$server = new Zend_Amf_Server();
$server->setProduction(false);
$server->addDirectory(dirname(__FILE__) .'/services/');
echo $server->handle();

We’re following the example in the manual here, there is another way however, a way I think is more direct and better. I couldn’t make it work though, for some reason I couldn’t coerce anything more than undefined out of what I received back in Flash (seems to work without much fuss in Flex but Flex isn’t an option here), undefined isn’t doing much for me hence we do it the orthodox roundabout way instead.

As you noticed we called User.find back in Flash, and this is User:

class User extends Service{
	
}

Not much but we get a lead through that extends Service so let’s go there:

class Service{
	
	public function getName(){
		return get_class($this);
	}
	
	public function tblName(){
		return strtolower(get_class($this));
	}

	private function getT(){ return Mysql::tbl($this->tblName()); }
	
	/**
	 * @param object $id
	 * @return object
	 */
	public function find($id){
		return $this->getT()->findOne($id->id);
	}
}

First of all, note the comments, they are yet again required, it’s either them or the messy inference code we had in the earlier tutorial it seems. Anyway, the Zend Amf guys have opted for comments (remember the earlier versions of AMFPHP, they also needed some coercion in yet a different way).

Which types correspond to which can be found in the wiki. Here we use objects anyway, object as parameter because the jQuery way we use requires it and object as return because we’re returning an associative array (works with array too, I tried).

The MySQL wrapper now has its own article.

If we return to the first listing, we’re passing the result to new User(result), let’s go there:

package
{
    public class User extends BaseObj{
        
        public var id:int;
        public var username:String;
        public var password:String;
        
        public function User(obj:Object):void {
        	super(obj);
        }
        
        public function sayHello(){
        	trace("Hi I'm "+this.username);
        }
    }
}

Aha, so it extends BaseObj, off we go:

package
{
    public class BaseObj{
        public function BaseObj(obj:Object):void {
        	for (var prop in obj) {
        		this[prop] = obj[prop];
			} 
        }
    }
}

This is where the magic happens, we’re looping through the id, username and password member variables and as you see they have to be named exactly the same in the User AS class and the database.

To recap, the above is a workaround due to the fact that the automatic class juggling didn’t work for me, I never got the stuff I got back from PHP to be liked by Flash, new user = User(result) always returned a no! No matter how hard I tried and double and triple checked that I had followed all the steps. I must’ve missed something, whatever I have what I want now anyway. It is also a simple and OK foundation to build on and add more stuff to.

Related Posts

Tags: , , , ,