Working with SabreAMF and Flex 3 using class mapping

After reading a few good articals (see list bellow) about using SabreAMF 1.x with Flex 2/3 my questions still stayed unanswered. I´ve used AMFPHP and saw the possibilities of class mappings, it gives nice code clarity despite the cost of dual implementation or (de)serialization differences. Very usefull when building large/critical applications with different progammers and designers. BlazeDS seems interesting too but the only thing I know about Jafa is how to spell it…including Phyton.

You´ll find a mountain of information about comparisons regarding the pros and cons of different AMF implementations by PHP however without solutions. In my humble opinion AMFPHP is, for now, usefull for projects with emphasis on the frontend with limited backend support. I’am looking for large OO structures and freedome of code with a less intrusive amf/php framework so SabreAMF sounds promising…

Unfortunately I couldnt find any suitable tutorials nor examples even failed over to www.metacrawler.com Anyways the lets get down to it!

We need to take the following steps and afterwards we have an example:

    Setup the php classes
    Setup an interfacing class
    Setup the flex classes
    Setup an amf gateway

php classes
In this simple real life situation I want to map three classes to and from Flex: they are used to manage users [CUser] within a group [CGroup] with the users stored in their member [CCollection] property. Indeed multiple groups could be stored in an collection aswell, but I leave that as homework :)

CObjects.php

class CUser extends CObject {

    public $id;
    public $username;

    public function __construct() {
        $this->id        = 0;
        $this->username  = "[user]";
    }

}

class CGroup extends CObject {

    public $id;
    public $name;
    public $members;

    public function __construct() {
        $this->id       = 0;
        $this->name     = "[group]";
        $this->members  = new CCollection();
    }

}

class CCollection extends SabreAMF_ArrayCollection {
}

From a php point of view nothing special here, just basic classes with a simple constructor and a few public properties to play with later on. However notice the members collection (line 26) with is derived from SabreAMF_ArrayCollection (line 33) included with the sabreamf. There are a few classes in flex which do not really map well to php and viceversa. Notably the ArrayCollection is a well known class in flex used frequently, but they already did that for you!

interfacing class
To keep things simple and structures eg. a single point of entry for AMF communication an seperate interface class is created like this:

CObjects.php

class CAMF {

    .
    .

    public function SendUser(CUser $user) {
        $user->username .= " is back!";
        return $user;
    }

    .
    .

    public function SendMembers(CGroup $group) {
        $c=$group->members->count();
        $group->name .= " members: $c";
        return $group;
    }

}

Every single function in here can be called one-on-one from flex! Cool hè :) I wrote a small class which handles requests from flex: CAmf. During coding and debugging small itches ocured during tracing and refreshing.. probably a cache thingy. Normally an RemoteObject is configured via a xml file describing various settings among the gateway url eg. http://servert/gateway.php It would occure connecting to the old one after changing it to for example http://servert2/gateway2.php .. grrrrr

The CAmf class builds an RemoteObject runtime and handles default event handling, otherwise a tedious job ;) Furthermore it simplifies calls to our php gateway with the call function, which I´ll describe later:

classes/CAmf.as

public function call(serviceName: String = "", methodName: String = "", arguments = null): void {
    this.amf.source = serviceName;
    var at: AsyncToken = this.amf.getOperation( methodName ).send( arguments );
}

flex classes
I haven´t found a mechanisme yet to put all flex classes in a single file. I´am not sure if its possible ´cause I remember reading somewhere it´s only possible to publish a single class per file..

classes/CUser.as

package classes
{

    [RemoteClass(alias="classes.CUser")]
    [Bindable]
    public class CUser extends CObject
    {
        public var id:int;
        public var username:String;

        //constructor
        public function CUser()
        {
            id          = 0;
            username    = "[username]";
        }

    }

}
classes/CGroup.as

package classes
{
    import classes.CCollection;

    [RemoteClass(alias="classes.CGroup")]
    [Bindable]
    public class CGroup extends CObject
    {
        public var id:int;
        public var name:String;
        public var members: CCollection;

        //constructor
        public function CGroup()
        {
            id      = 0;
            name   	= "[name]";
            members	= new CCollection();
        }
    }

}
classes/CCollection.as

package classes
{
    import mx.collections.ArrayCollection;

    [RemoteClass(alias="classes.CCollection")]
    [Bindable]
    public class CCollection extends ArrayCollection
    {
        public function CCollection(source:Array = null)
        {
            super(source);
        }
    }

}

Again nothing special except for a little experiment whith CCollection: it is derived from ArrayCollection just to see if ‘type’ changes affect amf communication.

amf gateway
You can pretty much use the example service callbackserver.php which comes with sabreamf. We´re going to build a callback service, register our php and flex classes and expand the onInvokeService a little bit:

gateway.php

SabreAMF_ClassMapper::registerClass('classes.CCollection'   ,'CCollection');
SabreAMF_ClassMapper::registerClass('classes.CUser'         ,'CUser');
SabreAMF_ClassMapper::registerClass('classes.CGroup'        ,'CGroup');

$server = new SabreAMF_CallbackServer();
$server->onInvokeService = 'InvokeService';
$server->exec();

function InvokeService($serviceName, $methodName, $arguments) {
    if (class_exists($serviceName)) {
        $serviceObject = new $serviceName;
        if (method_exists($serviceObject, $methodName)) {
            return call_user_func_array(array($serviceObject,$methodName),$arguments);
        } else {
            throw new Exception("Method '{$methodName}' does not exist in class '{$serviceName}'", 1);
        }
    } elseif (function_exists($methodName)) {
        return call_user_func($methodName, $arguments);
    } else {
        throw new Exception("Nothing to do for\r {$serviceName}->{$methodName}(".implode('|',$arguments).")", 2);
    }

}

This is where the magic takes place! The registerClass (lines 3-5) takes two arguments (both from an php point of view):

    1. $remoteClass:
    To import/include an flex class you need to supply the fullname including directory name relative to the project sources: our flex classes are stored in the directory classes/*.as
    The line [RemoteClass(alias=”classes.CUser”)] says send this class to php identified by “classes.CUser”. Consequently it will be mapped to CUser.

    2. $localClass:
    The name of the php class you want it to be mapped to.

For the coding stylists among us: hè what can I say!? I´am a bit neurotic when if-statements come into play..where´s the else??? I need to know it all. And yes I like exceptions too ;)

Sending and receiving objects using class mappings
Lets say we want to create a new user in flex and give it to php:

var amf: CAmf = new CAmf( "http://192.168.1.1/gateway.php" );

var userA: CUser = new CUser();
    userA.username = "Pepe";

amf.call( "CAMF", "SendUser", user );

After creating an connection to the gateway, we create an user and send it to our interfacing class CAMF using function SendUser with parameter user. If all went well an alert should popup with the username changed.

Lets say we want to create a group, add some users in flex and give it to php:

var amf: CAmf = new CAmf( "http://192.168.1.1/gateway.php" );

var group: CGroup = new CGroup();
    group.name = "P´s";

var userA: CUser = new CUser();
    userA.username = "Peter";
    group.members.addItem( userA );

var userB: CUser = new CUser();
    userB.username = "Patrick";
    group.members.addItem( userB );

amf.call( "CAMF", "SendMembers", group );

After adding the users, the group it is send by using CAMF->SendMembers( group ). If all went well again, it should return telling you how many members are found. The group is already deserialized before reaching the function, hence a hard type like (CGroup $group) is possible!

Download source files

references
Using Flex 2 RemoteObject and SabreAMF by Renaun Erickson
Getting started with SabreAMF by Wil Li
SabreAMF
AMFPHP
BlazeDS
PyAMF

July 9th, 2008 - Posted in flex, php | | 0 Comments