Wednesday, May 20, 2009

Using Flex and AMFPHP without a services-config.xml file

I've been working a bit with amfphp, sending data between Flex and PHP using the RemoteObject class. Initially I put the generic services-config.xml file in my application's src directory:
<services-config>
<services>
<service id="amfphp-flashremoting-service"
class="flex.messaging.services.RemotingService"
messageTypes="flex.messaging.messages.RemotingMessage">
<destination id="amfphp">
<channels>
<channel ref="my-amfphp"/>
</channels>
<properties>
<source>*</source>
</properties>
</destination>
</service>
</services>
<channels>
<channel-definition id="my-amfphp" class="mx.messaging.channels.AMFChannel">
<endpoint uri="http://localhost/amfphp/gateway.php"
class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>
</channels>
</services-config>
And changed my project properties to add this line to the Flex Compiler page:
-services services-config.xml

But I just came across this post which shows how to get around having to have an XML file by defining the channel at runtime.

So here is my simplified Flex code:
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.remoting.RemoteObject;
import mx.messaging.ChannelSet;
import mx.messaging.channels.AMFChannel;


var url:String = "http://localhost/amfphp/gateway.php";
var channel:AMFChannel = new AMFChannel("my-amfphp", url);
var channelSet:ChannelSet = new ChannelSet();
channelSet.addChannel(channel);
var ro:RemoteObject = new RemoteObject("amfphp");
ro.channelSet = channelSet;
// Specify the PHP class
ro.source = "TestService";
ro.addEventListener(FaultEvent.FAULT, function(event:FaultEvent):void {
    trace("Fault: " + event.fault);
});
ro.addEventListener(ResultEvent.RESULT, function(event:ResultEvent):void {
    trace("Result: " + event.result);
});
// the TestService class must have a public helloWorld function
ro.helloWorld();

You could also do the same in MXML:
<mx:ChannelSet id="channelSet">
<mx:channels>
<mx:AMFChannel uri="http://localhost/amfphp/gateway.php"/>
</mx:channels>
</mx:ChannelSet>
<mx:RemoteObject source="TestService" destination="amfphp"
channelSet="{channelSet}" id="remoteObject"
fault="trace('Fault: '+event.fault)"
result="trace('Result: '+event.result)"/>

And the corresponding PHP code (put the php file inside your webroot/amfphp/services directory):
<?php
class TestService {
    public function helloWorld() {
        return "Hello from PHP";
    }
}
?>

So what are the benefits of this approach? Well, I'm not sure. In my case it was better because I have one logging class that does all the remoting, and is used by many different Flex applications. So instead of having to duplicate the services-config.xml file in every flex application this way I could define my endpoint url (http://localhost/amfphp/gateway.php) in one place.

Here is another site that is useful when getting started with amfphp:
  • amfphp video tutorials
  • 12 comments:

    Will Gardner said...

    this is great, thanks. However, I am getting an error regarding the AMFChannel object. What class files do I need to import?

    Chris Callendar said...

    Hi Will,
    Here are the 5 classes that I'm importing:

    import mx.rpc.events.ResultEvent;
    import mx.rpc.events.FaultEvent;
    import mx.rcp.remoting.RemoteObject;
    import mx.messaging.ChannelSet;
    import mx.messaging.channels.AMFChannel;


    Feel free to post your error message and I'll try to debug it for you.

    Will Gardner said...

    I just found them...

    mx.rpc.events.ResultEvent;
    mx.rpc.remoting.RemoteObject;
    mx.messaging.channels.AMFChannel;
    mx.messaging.ChannelSet;

    Will Gardner said...

    Chris, I got this working... thanks to seeing how you did a few things.

    One more question, I discovered to my extreme delight that you can do:

    event.result.SOME_CLASS_OBJECT

    which allows you to reference class properties etc... but how/can you SEND variables so that your PHP class object can use them? For example, I tried this which didn't work:

    ro.deleteUserID = 20;

    hoping that in my php class file I could do something like this:

    "delete from users where userid = $myObject->deletUserID";

    Chris Callendar said...

    Hi Will,
    You can either directly pass simple strings, numbers, etc to PHP, or you can wrap them in an object. I'll show you have to use an object to pass information from Flex To PHP (mostly the same code as above):

    // call a public function in PHP called "deleteUser"
    var params:Object = new Object();
    params.deleteUserID = 20;
    params.message = "deleting";
    ro.deleteUser(params);


    // PHP code - $myObject is an associate Array
    public function deleteUser($myObject) {
    $message = $myObject["message"];
    $deleteUserID = $myObject["deleteUserID"];
    $query = "delete from users where userid = $deleteUserID ";

    }

    Make sense? At first I thought that $myObject->deletUserID would work, but it doesn't.
    Chris

    Will Gardner said...

    i see.... yeah that's what I wanted to do... pass an object full of items (I was able to pass a single param) but didn't think of sending an object of that single param over... thanks!

    btw, seems you've disabled copy and paste functionality or use of non-alphanumeric keys? At least it isn't working in firefox. Spam prevention?

    Chris Callendar said...

    I noticed that too yesterday, it drove me crazy! Instead I had to type in all the code above. And yes, it only happens on Firefox (IE and Chrome are fine). I'll look into it, maybe it's a security setting on my Blog.

    Chris Callendar said...

    Hmm, seems like it is a known problem with FireFox 3.1. And I believe it only happens when users are not signed into their Google Account. I've changed my comments from being "Embedded below post" to "Pop-up window" and now it works. Very weird.

    Jason said...

    You have a spelling mistake in the code:
    import mx.rcp.remoting.RemoteObject;

    Should be:
    import mx.rpc.remoting.RemoteObject;

    Chris Callendar said...

    Thanks Jason, nice catch.
    Fixed it above.

    Tejas Patel said...

    Facing problem when calling another remote object method from first remote object's result handler function.
    any idea?

    Chris Callendar said...

    Can you post some code or give me more information on what you are doing exactly?

    Are you listening for fault events on both remote objects? That can help to show what error is happening.