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:

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!

Comments

6 Responses to “Flex and PHP: remoting with AMFPHP”

  1. avidFlex on October 22nd, 2008 10:22 am

    Hi there,

    Been referring to what u posted abt AMFPHP and Flex and must say its really comprehensive. Just a question though, I keep getting AMF message errors in the localhost AMFPHP browser and this causes my application to unexpectedly quit during runtime. What i’m trying to do is create a simple flex form which links to AMFPHP and inserts the data into the database.

    Any suggestions?

    Thanks
    avidFlex

  2. Mihai Corlan on October 23rd, 2008 12:05 pm

    Hi there!

    What I would do:
    1. Make sure that there are no errors in the PHP class you want to call from Flex. Just open the scrip in a web browser and see if there are errors (you should have PHP configured to output the errors)
    2. Make sure there are no spaces or enters before the opening and after the closing tag < ?php / ?>
    3. Next I would try to reach the method using AMFPHP browser. Alternatively, you can add a small code at the top of the PHP class, to instantiate the class and call the method you want with the given arguments. This way you check that the PHP code is working correctly
    4. Finally, if all the steps before were passed, you probably have something wrong on the Flex side. Either you didn’t configure correctly the RemoteObject,or the VOs or something.

    While working with AMFPHP is not rocket science, it needs a lot of attention for details, and when you try for the first time it can be a little difficult.

    Good luck!

  3. Gilly on October 24th, 2008 12:13 am

    Thank you very much for this tutorial.. (Indeed one get quickly lost although it looks simple..)

    In your RemoteObject definition you define destination=”MyService” source=”MyService”
    I suppose the source is the actual php file in amfphp\services, but what should be the destination ? I thought it was the identifier of a destination defined in a config-file but I don’t see it in your services-config.xml …
    Thanks,
    G.

  4. Mihai Corlan on October 24th, 2008 9:15 am

    Hi Gilly,

    Both destination and source for AMFPHP need to be the name of the PHP class if you want not to use the remote-config.xml file. So, this is why you don’t need this configuration file.

    cheers

  5. Marty on November 6th, 2008 4:37 pm

    Thanks Mihai!

    I’d been banging my head against the keyboard trying to get AMFPHP to return my objects, until I ran across your article. No other tutorial seemed to mention that you needed to place them in services/vo/.

    Keep up the good work!

    Cheers!

  6. Flex and PHP: remoting with Zend AMF : Mihai CORLAN on November 13th, 2008 6:58 pm

    [...] way to get your data in Flex/AIR clients, I wanted to add a short post explaining how to do use it (here is another post I wrote on remoting with AMFPHP). Actually this post, is a part of a larger article [...]

Leave a Reply