Flex and PHP: remoting with AMFPHP

Since I recently received some requests for a simple example on getting up to speed with Flex, PHP, and AMF, I decided to write a tutorial on this topic. I will show you how to do remote procedure calls from Flex to PHP classes using AMFPHP. Soon I will post an article on how to do RPC using the Zend Framework and ZendAMF.

What is AMFPHP and why should you use remoting?

If you already know these answers, you may want to skip to the next section.
Let’s start by understanding of remote procedure calls. Remote procedure calls let Flex applications  make direct calls on the methods of your server side classes. Using BlazeDS or LCDS you can expose your Java and ColdFusion classes to the Flex application. However, if you use PHP you need a third party library on the server to expose PHP classes directly. Existing solutions include ZendAMF, WebOrb, and AMFPHP. This article focuses on remoting with AMFPHP, which uses a binary protocol (AMF) to serialize the messages. Because it is binary, it is more efficient in terms of bandwidth and server processing load than JSON or XML methods. If you want to see for yourself, James Ward has put together a nice benchmark.

AMFPHP is a PHP open source library that knows how to serialize and deserialize the AMF protocol, and thus lets you expose PHP classes to Flex applications. Another compelling reason for using remoting is code reuse. Because you can call methods on PHP classes and these methods can return PHP objects, you don’t have to modify your existing code to output JSON or XML.

As I said earlier, AMFPHP remoting uses AMF for serializing messages between the server and Flex client. And it offers a nice feature to map an ActionScript class to a PHP class. For example, suppose you want to display in Flex the information from a table with the following structure:

contacts
-------------------------------
id      primary key int
name    varchar(255)
email   varchar(255)

When using remoting, you create an ActionScript class to model this data in the client and a PHP class to model the same data on the server. When you create the PHP class that you want to call from Flex, you add a method that, for example, retrieves all the contacts from the table. This method will return an array of PHP model classes. This is what you need to do. And in Flex you will get an array of ActionScript objects. All the conversions from PHP to AMF format and from AMF format to ActionScript objects are done automatically for you by  Flex and AMFPHP.

When you use XML or JSON for remoting, usually you need extra steps in Flex to process the data in order to display or store it.

Let’s look at a working example.

Step 1: Install the AMFPHP and understand its structure

While it is not hard to create a Flex application that makes RPCs using AMFPHP, I found some possible glitches when you do it for the first time. If you missed something, you will end up with errors such as:

  • the array you retrieve in Flex is not of your type, but a generic object
  • in PHP you don’t get a PHP VO class as an argument when you call a method, but an associative array

It is my intention to explain all the small things you need to take care of, so you can get it right.
First grab the AMFPHP library archive, unzip it somewhere on your machine and then copy the amfphp folder on your PHP webserver. From now on, I will refer to this folder as the installation folder. On my machine this folder is c:/htdocs/amfphp and the URL is http://localhost/amfphp.

If you open this folder you will notice a folder named “browser”. When you open this folder in your browser  (on my machine http://localhost/amfphp/browser) you will get a Flex application that lets you test all the exposed PHP classes:

AMFPHP: Browser service

Beside this folder, there is “services” folder. This is very important. In the “services” folder you need to place all the PHP classes you want to expose to Flex code. Also, all the PHP Value Object classes you want to use for modeling the data must be inside the folder “services/vo/” + <the package name as folders> For example if you have the ActionScript class org.corlan.VOAuthor and you want to map to a PHP class with the same name, then the PHP class should be in “services/vo/org/corlan/VOAuthor.php”.

These are the default configurations for AMFPHP. If you don’t like them you can open the globals.php file from inside of the installation folder and make changes.

Step 2: Create the PHP code

Let’s create a small PHP class that does two things:

  1. Reads and returns all the records from a table
  2. Offers a method to update one record

I use MySQL, and the table creation SQL is as follows:

CREATE TABLE `authors_aut` (
  `id_aut` int(11) NOT NULL auto_increment,
  `fname_aut` varchar(255) NOT NULL,
  `lname_aut` varchar(255) default NULL,
  PRIMARY KEY  (`id_aut`),
  UNIQUE KEY `fname_aut` (`fname_aut`,`lname_aut`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;

--
-- Dumping data for table `authors_aut`
-- 

INSERT INTO `authors_aut` VALUES (1, 'Dantes', 'Alighierie');
INSERT INTO `authors_aut` VALUES (4, 'Niccolo', 'Machiavelli');
INSERT INTO `authors_aut` VALUES (3, 'Umberto', 'Eco');
INSERT INTO `authors_aut` VALUES (2, 'William', 'Shakespeare');

So I create a PHP file inside the amf/service/ folder called MyService.php. Inside of this file I create a PHP class with two methods: getData() and saveData(). The complete code is here:

<?php
require_once ('./vo/org/corlan/VOAuthor.php');

//conection info
define( "DATABASE_SERVER", "localhost");
define( "DATABASE_USERNAME", "mihai");
define( "DATABASE_PASSWORD", "mihai");
define( "DATABASE_NAME", "flex360");

class MyService {

    public function getData() {
        //connect to the database.
        $mysql = mysql_connect(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD);
        mysql_select_db(DATABASE_NAME);
        //retrieve all rows
        $query = "SELECT id_aut, fname_aut, lname_aut FROM authors_aut ORDER BY fname_aut";
        $result = mysql_query($query);

        $ret = array();
        while ($row = mysql_fetch_object($result)) {
            $tmp = new VOAuthor();
            $tmp->id_aut = $row->id_aut;
            $tmp->fname_aut = $row->fname_aut;
            $tmp->lname_aut = $row->lname_aut;
            $ret[] = $tmp;
        }
        mysql_free_result($result);
        return $ret;
    }

    public function saveData($author) {
        if ($author == NULL)
            return NULL;
        //connect to the database.
        $mysql = mysql_connect(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD);
        mysql_select_db(DATABASE_NAME);
        //save changes
        $query = "UPDATE authors_aut SET fname_aut='".$author->fname_aut."', lname_aut='".$author->lname_aut."' WHERE id_aut=".  $author->id_aut;
        $result = mysql_query($query);
        return NULL;
    }
}
?>

The code is pretty simple. No abstract database layer, just the simplest PHP code to make something useful.

As you can see in the above code, I used the VOAuthor PHP class. So, now it’s time to create this class (this class models one row from the table). Basically this class will have one field for each row of the table (I will keep the name of the fields similar to the table fields) and an extra field that tells AMFPHP how to serialize the class when sending back the message. Let’s see the code and then I will explain a little more about this field:

<?php
class VOAuthor {

    public $id_aut;
    public $fname_aut;
    public $lname_aut;

    // explicit actionscript class
    var $_explicitType = "org.corlan.VOAuthor";
}
?>

The extra field is $_explicitType, and its value is the fully qualified ActionScript Value Object I intend to use in the Flex application to model the data. If you don’t configure this field correctly, then in the Flex app you will not get your strongly typed ActionScript class, but a dynamic object.

Important! Make sure you do not add empty spaces or other chars after the PHP closing tag. If you leave extra chars, the output buffer will be flushed and the message that AMFPHP sends to the Flex client will not be correctly formatted.

If you go back to the browser service of AMFPHP, you can try the code — select the getData method and click the Call button. You should get an array of objects.

Testing the service

Step 3: Create the Flex project

We have all the PHP code in place, it is time to create the Flex application. First step is to create a Flex project using the PHP server type (you can read here an article I wrote on how to create Flex and PHP projects if you want to find more tips and tricks). This is the first page of the wizard:

Creating the Flex project

Click “Next” and then “Finish”.

Step 4: Create the ActionScript code

Now let’s create the ActionScript value object class, VOAuthor. Right click on the “src” folder and choose New > ActionScript class (make sure you enter the package name):

amfphp_4

The code for this class is:

package org.corlan {

    [RemoteClass(alias="org.corlan.VOAuthor")]
    [Bindable]
    public class VOAuthor {

        public var id_aut:int;
        public var fname_aut:String;
        public var lname_aut:String;
    }
}

How does Flex know to serialize the ActionScript VOAuthor class to the PHP VOAuthor? Because of the tag RemoteClass. Here you enter the name of the PHP class you want to use and the path from the “amfphp/vo/” to the class as the package name. Thus I end up with “org.corlan.VOAuthor”.

It is time to put all these together and create the ActionScript code that makes the call to the PHP class and displays the info. For this, open the php_amf.mxlm file if it isn’t already open and add this code:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <mx:Script>
        <![CDATA[
            import mx.controls.dataGridClasses.DataGridColumn;
            import mx.events.DataGridEvent;
            import org.corlan.VOAuthor;
            import mx.controls.Alert;
            import mx.rpc.events.FaultEvent;
            import mx.rpc.events.ResultEvent;
            import mx.collections.ArrayCollection;

            /**
             * This function is called when an item was edited in the data grid.
             * Calls the saveData() method on the PHP server 
             */
            private function save(event:DataGridEvent):void {
                var dataGrid:DataGrid = event.target as DataGrid;
                var dsColumnIndex:Number = event.columnIndex;
                var col:DataGridColumn = dataGrid.columns[dsColumnIndex];
                var newValue:String = dataGrid.itemEditorInstance[col.editorDataField];
                var dsFieldName:String = event.dataField;
                var author:VOAuthor = event.itemRenderer.data as VOAuthor;
                if (newValue == author[dsFieldName])
                    return;
                //get the new value for the first name or last name
                author[dsFieldName] = newValue;
                myRemote.saveData(author);
            }
        ]]>
    </mx:Script>
    <!-- this is the RemoteObject  used to make the RPC calls -->
    <mx:RemoteObject id="myRemote" destination="MyService" source="MyService"
            endpoint="http://localhost/amfphp/gateway.php" showBusyCursor="true"/>

    <mx:VBox top="30" left="100">
        <mx:Button label="Get data" click="{myRemote.getData()}" />
        <mx:DataGrid id="myGrid" dataProvider="{myRemote.getData.lastResult}" editable="true" itemEditEnd="save(event)">
            <mx:columns>
                <mx:DataGridColumn dataField="id_aut" editable="false"/>
                <mx:DataGridColumn dataField="fname_aut"/>
                <mx:DataGridColumn dataField="lname_aut"/>
            </mx:columns>
        </mx:DataGrid>
    </mx:VBox>
</mx:Application>

For remoting, Flex uses RemoteObject. As you can see in my code, I create one instance of this object. Then I configure the endpoint to work with AMFPHP and the PHP class (MyService). For this I add the URL to the gateway.php file as the value of the attribute endpoint, and I set MyService as the value of the destination and source attributes.

The UI of the application is very simple: a button to call the getData() method from the server and a data grid for displaying and editing the data. The binding between the data retrieved from the server and the data grid is done directly on the data grid using the property lastResult: dataProvider=”{myRemote.getData.lastResult}”.

There is an event listener registered on the data grid for the event of ending the editing of a cell. Inside this event listener, I call the saveData() method using the instance of the currently edited Value Object.

Final words

That’s it folks! If you are too lazy to set up the project and copy and the code, you can download the project from here. Read the readme.txt after you import the project in Flex Builder using the Import wizard > Flex Builder. I will post another article on ZendAMF soon. See you!

111 thoughts on “Flex and PHP: remoting with AMFPHP

  1. I tried the amf in object and with about 9000 records result (my custom object) this is the response:

    ZendAMF 12 sec, 650Kb
    AMFPHP1.9 11 sec, 330 Kb
    AMFPHP 2.0 13 sec, 2,4 Mb

    Why the latest amfphp is so slow and so big? Also the 1.9 isn’t so speed than ZendAmMF, just about 1 second … in web the response are much different than mine, of the order 10x favor to amfphp!
    I would to change from zend to amfphp, but maybe it’s soon.
    thanks

  2. Thanks…
    GOD BLESS YOU!!!
    Its really amazing!!
    i gone through lot of reference for AMFPHP
    but not fully working…. but this is really good.

    thanks lot

  3. Hi,

    Thanks for the great article. It’is very useful.

    But I’d like to add something :
    while I was making my own business and I had a problem : the object received in my FLEX application was a dynamic Object, not my strongly typed object. So I took your example, and tried to add my code. But what I received was always the same : org.corlan::VOAuthor with your code and Object with mine.
    The trick? That line :
    var author:VOAuthor = event.itemRenderer.data as VOAuthor;
    in the save method in FLEX. I didn’t used that method, but it was there.

    Here is the explanation found after some hours searching a solution :

    “Ensure that you have at least one reference to the class somewhere in your codebase.

    This is a common trap, especially when first developing a remote call, and before you’ve actually consumed the type in any code anywhere.

    If the class is not referenced, it’s not compiled in, and therefore, doesn’t get registered”

    Hope this will be helpful for someone.

  4. This was VERY helpful. I had one error on my setup. In case anyone else has the same issue… I’m using Lion, php 5.3.10, and AmfPHP 2.1 (from http://sourceforge.net/projects/amfphp/files/amfphp-2.1.zip/download). I was getting this error:

    Failed opening required ‘./vo/org/corlan/VOAuthor.php’ (include_path=’.:’) in /Library/WebServer/Documents/Amfphp/Services/MyService.php on line 2

    I changed the path to “vo/org/corlan/VOAuthor.php” (just remove ./) and it fixed it.

  5. I am a newbee in flex and am trying to insert data into mysql with amfphp.It does not work but am able to retrieved data.

    And the network monitor shows the data been sent but no data is retrieved when passing paramter to the server for the insertion.

    Please someone helps me out

  6. Some errors appear to have sneaked in above. I was able to get strong-typed objects when fixing these two:

    1. The require_once line should NOT include ./
    2. The remote class and the actionscript class should be referred to WITHOUT any package names in front.

    This may help others!

  7. I am trying Amfphp 2.1. And it’s working too slow.Charles proxy showed up with these. I suppose latency is the main culprit here ? if yes then how can i reduce network latency? Can you please help Mr Corlan ?

    Timing:
    Duration 7.35 sec
    Request Duration 1.56ms
    Response Duration 1.56ms
    Latency 5.78sec

    Size:
    Total Size 69.37KB
    Response Compression 73.55%(gzip)

  8. very helpful
    but i have a problem that the objects returned in my array miss all their fields except one field.
    i tested my code that returns the array of VOs in a separate php project and it returns the whole object.
    but when i use amfphp only one field of VO returns for all elements of the returned array
    can anyone tell me what may be the reason for that?

  9. This was a really good example. If anyone is interested I have found another tutorial that specifically covers Flex -> PHP remoting with date objects. Its not as detailed as this tutorial because it already assumes prior knowledge, but still a good extension to this one that focuses on dates.

    http://www.brentknigge.com/?q=node/494

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>