tutorials flex remoting_02
Class mapping in AS3
General information
For those that have been using the AMFAction Message Format protocol for a while, in particular Flash Remoting, you probably use personalised classes in your projects.
I’m going to base my article on AMFPHP so I don’t know if what I am going to talk about will be valid in JAVA, NET, etc. If you want to work in any of these contexts you will have to carry out your own tests as well as what I am going to talk to you about :)
We can see a difference in Class mapping in the output (Flash to server) and on input (Server to Flash). In AMFPHP there are many solutions to use this functionality, I personally have edited the file advancedsettings.php in AMFPHP 1.2 which is found at the same level as the gateway.php file and I add my mappings to the incoming and outgoing lists. You can find on the AMFPHP website all the explanations on this subject in the official documentation: Class Mapping
Example of Class Mapping in AS2
To illustrate my working methods I am going to create an example with a User class which will allow me to create instances with 3 properties “nameâ€, “age†and “urlâ€. I will then be able to try to create an instance of this class in my PHP code which I will then send directly to my animation via an AMFPHP service.
1 - Let’s start by creating a services/test/ directory of my AMFPHP installation.
2 - I then create the User class in a User.php file in the services/test directory .
<?php class User { // ----o Constructor function User ( $name, $age , $url ) { $this->name = $name ; $this->age = $age ; $this->url = $url ; } // ----o Public Properties var $age ; var $name ; var $url ; } ?>
3 - I modify the advancedsettings.php file found at the root of my directory containing AMFPHP:
<?php /** * This file defines settings regarding custom class mappings * Custom class mapping is an advanced feature that enables mapping instances of * custom Flash objects to PHP objects and vice-versa. If you want to implement * VOs (value objects) this is the perfect feature for this * * If you have no idea what a VO is chances are you don't really care about these * settings * * Most of the time you will probably want to change the outgoing settings only * since incoming mappings are done for you automatically */ //One may choose to put mapped classes (incoming) outside of the services folder also $gateway->setBaseCustomMappingsPath('services/vo/'); //Set incoming mappings $incoming = array( ); $gateway->setCustomIncomingClassMappings($incoming); //Set outgoing mappings $outgoing = array( 'user' => 'test.User' ); $gateway->setCustomOutgoingClassMappings($outgoing); ?>
I don’t use the vo/ directory as I like creating classes in directorys directly contained in the services directory. But you can do as you want to on this one!
4 - I then create the Test class in my services/ directory to create an instance of my User class in Flash:
<?php require("test/User.php") ; class Test { // -----o Constructor function Test () { $this->methodTable = array ( "getUser" => array( "description" => "Returns a user.", "access" => "remote", "returns" => "test.User", ) ) ; } // ----- Methods /** * @desc Returns User instance. * @access remote */ function getUser( $name, $age, $url ) { return new User( $name, $age, $url ) ; } } ?>
5 - I will now create a quick example in Flash AS2 to create an instance of the User class directly via my AMFPHP service.
You need to start by creating the AS2 test.User class:
/** * @author ekameleon */ class test.User { // ----o Constructor public function User(name:String, age:Number, url:String) { if ( name != null ) this.name = name ; if ( age > 0) this.age = age ; if ( url != null) this.url = url ; trace( "> User constructor : " + this.name ) ; } // -----o Public Properties public var age:Number ; public var name:String ; public var url:String ; // ----o Public Methods static public function register():Void { Object.registerClass("test.User", test.User) ; } public function toString():String { return "[User:" + name + ", age:" + this.age + ", url:" + this.url + "]" ; } }
Then in Flash, I open a new document and type into the timeline the following code:
import mx.remoting.*; import mx.rpc.*; import mx.utils.Delegate; import mx.remoting.debug.NetDebug; import test.User ; //User.register() ; var result:Function = function ( result:ResultEvent ) { trace("> result : " + result.result) ; } var error:Function = function( fault:FaultEvent ):Void { trace( "> Error : " + fault.fault.faultstring ); } var gatewayUrl:String = "http://localhost/work/vegas/php/gateway.php"; var service:Service; var responder:RelayResponder = new RelayResponder(this, "result", "error") ; service = new Service(gatewayUrl, null, "Test", null, responder); service.getUser("eka", 29, "http://www.ekameleon.net/blog/" );
I am using Macromedia/Adobe’s AS2 MX framework for now to generate the remote service.
I publish the animation remembering to launch my local server before ;) (I use WAMP for my tests).
I get the following message in my output panel:
> result : [object Object]
The object that I receive using my getUser() method is not an instance of a User class. You must not forget that in AS1 or AS2 you cannot use the Object.registerClass method to save your classes on the client side for data transfers via a Remoting service, a LocalConnection or a SharedObject.
If you look closer at the AS2 test.User class above, I have inserted a static User.register() method within it which allows you to initialise the save when you like. The obligatory condition to create a test.User instance in your animation is therefore to launch a static method before launching a remote service method.
I therefore modify the main code of my animation by taking away the comment before the instruction line User.register().
import mx.remoting.*; import mx.rpc.*; import mx.utils.Delegate; import mx.remoting.debug.NetDebug; import test.User ; User.register() ; // here is the save of the User class var result:Function = function ( result:ResultEvent ) { trace("> result : " + result.result) ; } // etc....
If you re-launch your example you will now get the following in your output panel:
> User constructor : eka > result : [User:eka, age:29, url:http://www.ekameleon.net/blog/]
At the reception of the object of the PHP service, a new User type instance is created (see the first line in the output panel) and I can then verify that the name, age and url properties are defined as they should be.
It is with this type of technique that it is possible using a MYSQL request to directly create a RecordSet object type in Flash. We can see that by looking at this class in the mx.remoting package that the RecordSet object generated using the AMF service uses a serviceInfo internal property which allows it to initialise itself upon the calling of the constructor once the object is deserialised.
Problem in AS3.
I now have to talk to you about the problem that led me to write this article.
What happens in AS3 if we want to create a test.User instance via a Remote service like in AS2?
I will quickly reproduce the above exercise in AS3. You just need to remember before starting this AS3 test that the Object.registerClass method no longer exists and you now need to use the flash.net.registeClassAlias method instead.
1 - I create a new ActionScript project in Flex which I call TestClassMapping (I base the project’s source path in the src/ directory).
2 - I create the test.User class in the directory src/test/
package test { import flash.net.registerClassAlias ; public class User { // ----o Constructor public function User(name:String="", age:uint=0, url:String="") { trace( "> User constructor : " + this ) ; this.name = name ; this.age = age ; this.url = url ; } // -----o Public Properties public var age:uint ; public var name:String ; public var url:String ; // ----o Public Methods static public function register():void { registerClassAlias("test.User", User) ; } public function toString():String { return "[User name:" + name + ", age:" + this.age + ", url:" + this.url + "]" ; } } }
Then I put the contents of my TestClassMapping class in place (default Application class):
package { import flash.display.Sprite; import flash.net.NetConnection; import flash.net.ObjectEncoding; import flash.net.Responder; import test.User ; public class TestClassMapping extends Sprite { // ----o Constructor public function TestClassMapping() { User.register() ; // save the class. var url:String = "http://localhost/work/vegas/php/gateway.php" ; nc = new NetConnection() ; nc.objectEncoding = ObjectEncoding.AMF0 ; nc.connect( url ) ; var method:String = "Test.getUser" ; var responder:Responder = new Responder(_onResult, _onFault) ; nc.call(method, responder , "eka" , 29, "http://www.ekameleon.net/blog/") ; } // ----o Public Properties public var nc:NetConnection ; // ----o Private Properties private function _onResult ( result : Object ) : void { trace( "> result : " + result ) ; } private function _onFault ( fault : Object ) : void { var s : String = ""; for( var i : String in fault ) { s+= i + " " + fault[i] + "\n"; } trace("> fault : " + s) ; } } }
I use as in my AS2 example the static User.register () method so that the deserialisation can be done correctly.
3 - I start my application in debug mode and get the following in the Flex output:
> User constructor : [User name:null, age:0, url:null] > result : [User name:eka, age:29, url:http://www.ekameleon.net/blog/]
Everything seems to have gone well, the generated object is the test.User type, but if you compare the output of my AS2 example and this one you will see there is a difference!
In the class constructor my name, age and url properties aren’t filled in upon publishing the constructor function as in AS2. It is therefore impossible to use these properties in the class constructor… It seems that the AMF deserialisation firstly creates the instance and then fills in the object properties. It is therefore impossible to use the deserialised object value properties in the instance constructor. You should bear in mind that I haven’t tested if it’s the same in AMF3? I hope not!
AS3 remoting with ASGard.
I discovered the problem by taking the RecordSet class which I developed in AS2 in my asgard.net.remoting package to cable a new AS3 implementation.
In AS2 I use the same technique in the constructor of my class as Adobe/Macromedia with the serverInfo property and it is in fact impossible as I have demonstrated to you to directly recuperate the value of the property in the class constructor to initialise the instance!
I have however found a solution to remedy the problem by transforming this property into a virtual property (get/set) and therefore even if the property initialises itself after the launch of the constructor the function allows me to launch the initialisation of the instance at the right time.
It is important to note that I updated my AS3 version of ASGard with its asgard.net.remoting package. You can find the ASGard sources in the src of VEGAS.
Here is a quick example of the use of ASGard which takes the example of the article:
package { import asgard.events.ActionEvent ; import asgard.events.RemotingEvent ; import asgard.net.remoting.RemotingService; import asgard.net.remoting.RemotingAuthentification; import flash.display.Sprite ; import test.User ; public class TestAsgardRemoting extends Sprite { // ----o Constructor public function TestAsgardRemoting() { // ----o Register your shared Class. User.register() ; // ----o Create Service var service:RemotingService = new RemotingService() ; service.addEventListener(RemotingEvent.ERROR, onError) ; service.addEventListener(RemotingEvent.FAULT, onFault) ; service.addEventListener(ActionEvent.FINISH, onFinish) ; service.addEventListener(RemotingEvent.RESULT, onResult) ; service.addEventListener(ActionEvent.START, onStart) ; service.addEventListener(RemotingEvent.TIMEOUT, onTimeOut) ; service.gatewayUrl = "http://localhost/work/vegas/php/gateway.php" ; service.serviceName = "Test" ; service.methodName = "getUser" ; service.params = ["eka", 29, "http://www.ekameleon.net"] ; // ----o Launch Service service.trigger() ; } // ----o Public Methods public function onError(e:RemotingEvent):void { trace("> " + e.type + " : " + e.code) ; } public function onFinish(e:ActionEvent):void { trace("> " + e.type) ; } public function onFault(e:RemotingEvent):void { trace("> " + e.type + " : " + e.getCode() + " :: " + e.getDescription()) ; } public function onProgress(e:ActionEvent):void { trace("> " + e.type ) ; } public function onResult( e:RemotingEvent ):void { trace("-----------") ; trace("> result : " + e.result) ; trace("-----------") ; } public function onStart(e:ActionEvent):void { trace("> " + e.type ) ; } public function onTimeOut(e:RemotingEvent):void { trace("> " + e.type ) ; } } }
If you want I will show you the use of my asgard.net.remoting package in more detail. I just need to wire in the proxy access of the services as I do in AS2. I think that that solution is already stable enough for your daily use in AS3.
You should note that I have been able to completely re-code the RecordSet in the asgard.data.remoting package even if I still need to wire in certain non-urgent functionality such as page management etc. You can therefore easily create a RecordSet instance using my RemotingService class with AMFPHP when a MYSQL request returns results back to your animation as in AS2.
I find it strange that Adobe have not quickly created tools for Remoting in AS3. Maybe they are concentrating on version 3 of the AMF protocol?
Whatever the answer, I quite urgently needed my AS2 and AS3 tools and I’m happy that I was able to quickly put them in place (even if there were some strange results!).
If you haven’t yet used my OpenSource framework, you can download the sources of VEGAS and of ASGard on the SVN of the project. More info can be found on this subject in the following articles:
By ALCARAZ Marc aka EKAMELEON (2006). You can view this tutorial and its comments on my blog.
Mediabox Training Centre © 2000 - 2008 All rights reserved.
Adobe Authorized Training Centre. State convention under number 25 14 02167 14.
Mediabox : SARL au capital de 62.000€ - Activity number: 25 14 02167 14 - SIRET : 493 716 468 00027
MEDIABOX, 102 Avenue des Champs Elysées, 75008 PARIS - Tel. +33(0)2.31.91.96.89 - Fax. +33(0)2.72.68.56.42


