Flex and PHP: remoting with Zend AMF

The latest PHP library to add support for AMF and remoting is Zend Framework. The preview prelease version 1.7 offers a new component Zend_AMF that lets you create Flex applications that talk to PHP backends using remoting. Since I am a big fan of remoting as a way to get data to your Flex/AIR clients, I wanted to add a short post explaining how to use it. Here is another post I wrote on remoting with AMFPHP. Actually this post is a part of a larger article I did for Adobe Developer Connection. I want to keep it more focused, so I wrote this one.

You can download a Flex Builder project that contains the code I explain in this article from here. Inside of the archive you will find a readme.txt file explaining what to do with it.

Installing the Zend Framework

After downloading the Zend Framework 1.7 archive, extract the files. Next, you have to add the library folder to your PHP include path. Open the php.ini file and add the path to the library folder to the include_path; on my machine looks like:
include_path = “c:htdocszend_frameworklibrary”

Next, save the file and restart your web server. You can read more about installing Zend Framework here. With this, you’ve completed the “installation” of Zend Framework.

What is AMF and remoting and why should you use it?

If you already know these answers, you may want to skip to the next section. Let’s start by understanding remote procedure calls. Remote procedure calls let Flex applications  make direct calls on the methods of your server side classes. Using BlazeDS or LiveCycle Data Services 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 Zend AMF, WebORB, and AMFPHP. This article focuses on remoting with Zend AMF. AMF is a binary protocol for serializing 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 how much more efficient it is, James Ward has put together a nice benchmark.

Zend AMF is a PHP library that knows how to serialize and deserialize the AMF protocol (it is part of the Zend Framework starting with version 1.7), 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 noted earlier, Zend AMF remoting uses AMF to serialize messages between the server and Flex client. It also offers the ability 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 VO classes, and in Flex you will get an array of ActionScript objects. All the conversions from PHP objects to AMF to ActionScript objects are done automatically for you by  Flex and Zend AMF.

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

Let’s look at a working example.

Create the Flex PHP project

Usually, when I work with Flex and PHP projects, I prefer to use Flex Builder and Zend Studio installed together. It is possible, however, to work with Flex Builder and a PHP plugin to help you with the PHP code. Either way, you should create a Flex project that uses PHP on the server side (if you plan to use Zend Studio and Flex Builder, first create a Zend PHP Project, then use the Add Flex Nature wizard to add Flex PHP nature on the project). This way you streamline the deployment of the SWF file (the compiled result of the Flex project) to the PHP server. I chose to create a new project called “flex_php”.

Next, create a folder inside the PHP server root named “zendamf_remote”, and add this folder to the project. Choose New > Folder, and then click on the Advanced button. If you want to have the source files for the Zend Framework available to your project, and you use Zend Studio too, then open the properties page for the project, go to the PHP Include Path > Libraries tab, and add an External Folder pointing to the place where the Zend Framework is installed.

Create the PHP code

In the “zendamf_remote” folder, create three PHP files: MyService.php, VOAuthor.php, and index.php. Open the MyService.php page and paste the following code (you need to update the connection information for your specific database setup; to do this, look for the four constants at the top of the class):

<?php
require_once('VOAuthor.php');
//connection info
define("DATABASE_SERVER", "localhost");
define("DATABASE_USERNAME", "mihai");
define("DATABASE_PASSWORD", "mihai");
define("DATABASE_NAME", "flex360");

class MyService {
/**
* Retrieve all the records from the table
* @return an array of VOAuthor
*/
public function getData() {
     //connect to the database.
     //we could have used an abstracting layer for connecting to the database.
     //for the sake of simplicity, I choose not to.
     $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;
}
/**
* Update one item in the table
* @param VOAuthor to be updated 
* @return NULL
*/
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;
}
}
?>

This is the class you will call from Flex. It has two methods: one to get all the records from the table, and another to update the values for one record.

Let’s create the code for the Value Object, the data model. This is used by the MyService class to wrap one row from the table. Thus, the method getData() returns an array of VOAuthor, and the method saveData() receives one argument: the VOAuthor of the row that was changed. Open the file VOAuthor.php and add this code:

<?php
class VOAuthor {
     public $id_aut;
     public $fname_aut;
     public $lname_aut;
}
?>

As you can see, this class is very simple; it just provides the same members as the fields from the table. Finally let’s create the code for index.php file. This is the plumbing code that expose the MyService class to Flex clients with the help of the Zend AMF. Add the following code:

<?php
require_once('Zend/Amf/Server.php');
require_once('MyService.php');

$server = new Zend_Amf_Server();
//adding our class to Zend AMF Server
$server->setClass("MyService");
//Mapping the ActionScript VO to the PHP VO
//you don't have to add the package name
$server->setClassMap("VOAuthor", "VOAuthor");
echo($server -> handle());
?>

I use an instance of Zend AMF server to create a PHP end point that can be called from Flex. Then I register the MyService class to the server, thus I can call this class from Flex. And finally I map the ActionScript data model (VOAuthor) to the PHP VOAuthor data model.

When you use remoting, you get the casting of the data to the right type for free. For example, MyService.getData() method returns an array of VOAuthor PHP objects. However, as you will see later, in Flex the result is an array of VOAuthor ActionScript objects.

Creating the Flex application

Now that you have the PHP code in place, you are ready to create the Flex code that will call the PHP class. I want the Flex application to have a button that gets the data from the server, uses a data grid to display the data, and enables the user to edit any cell (except ids) within the data grid. Whenever a cell is edited, the update is sent automatically to the server and saved to the database as well.

First, be sure to select the Flex perspective from the top right icons of Eclipse.

The next thing you need to do is to create a configuration file that Flex can use to reach the PHP service. Create the file services-config.xml in the root of the project. Open the file and add this code:

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
    <services>
        <service id="amfphp-flashremoting-service" class="flex.messaging.services.RemotingService" messageTypes="flex.messaging.messages.RemotingMessage">
            <destination id="zend">
                <channels>
                    <channel ref="my-zend"/>
                </channels>
                <properties>
                    <source>*</source>
                </properties>
            </destination>
        </service>
    </services>
    <channels>
        <channel-definition id="my-zend" class="mx.messaging.channels.AMFChannel">
            <endpoint uri="http://localhost/zendamf_remote/" class="flex.messaging.endpoints.AMFEndpoint"/>
        </channel-definition>
    </channels>
</services-config>

Be sure to check the endpoint node (at the bottom of the file); your URL to the zendamf_remote folder might be different. Set the value appropriately for your setup.

Now you need to tell Flex Builder to use this file when compiling the project. Right click on the project name in the Project Explorer and choose Properties. Select Flex Compiler and add the following to Additional compiler arguments field: -services “absolute_path_to_the_file/services_config.xml”:

Adding services_config to compile arguments

You will use a RemoteObject to communicate with the server, so add a mx:RemoteObject tag. You need to set the source attribute to MyService (this is the PHP class name) and the destination to zend – this is the destination created in the services-config.xml file. Also give a name to this object by adding an id attribute and set it to myRemote. Set the attribute showBusyCursor to true (whenever a call is made this will render the mouse icon as a watch, until a response from the server is received). The code should look like this:

<mx:RemoteObject id="myRemote" destination="zend" source="MyService" showBusyCursor="true">
</mx:RemoteObject>

Now you need to declare the methods you want to call on the PHP class, and add the listeners for fault and result events. The code is:

<mx:RemoteObject id="myRemote" destination="zend" source="MyService" showBusyCursor="true" fault="faultListener(event)">
     <mx:method name="getData" result="getDataListener(event)"/>
     <mx:method name="saveData" result="saveDataListener(event)"/>
</mx:RemoteObject>

Next you need a UI to make the call to the server and display/edit the data. A button and a data grid will do. Add this code above the RemoteObject code:

<mx:VBox top="30" left="100">
           <mx:Button label="Get data" click="{myRemote.getData()}" />
           <mx:DataGrid id="myGrid" editable="true" itemEditEnd="save(event)"/>
</mx:VBox>

As you can see, the button calls the getData() method on the remoteObject. The data grid has an event listener registered for the itemEditEnd event.

The last step is to create the listeners you declared. For this, add an mx:Script tag to your MXML application and define four functions in it:

<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;
           /**
            * listener for the data grid's itemEditEnd event
            */
           privatefunction save(event:DataGridEvent):void {
                //we don't want to update the id of the item
                if (event.dataField == "id_aut") {
                     event.preventDefault();
                     return;
                }
                //retrieve the new value from the item editor instance
                var dataGrid:DataGrid = event.target as DataGrid;
                var col:DataGridColumn = dataGrid.columns[event.columnIndex];
                var newValue:String = dataGrid.itemEditorInstance[col.editorDataField];
                //retrieve the data model that was edited
                var author:VOAuthor = event.itemRenderer.data as VOAuthor;
                // if the value wasn't change, exit
                if (newValue == author[event.dataField])
                     return;
                //update the model with the new values     
                author[event.dataField] = newValue;
                //call the remote method passing the data we want to be saved
                myRemote.saveData(author);
           }
           /**
            * Result listener for get data operation
            */
           privatefunction getDataListener(event:ResultEvent):void {
                //set the result array as data provider for the data grid         
myGrid.dataProvider = event.result as Array;
           }
           /**
            * Result listener for save data operation
            */
           privatefunction saveDataListener(event:ResultEvent):void {
                Alert.show("The data was saved!");
           }
           /**
            * Fault listener for RemoteObject
            */
           privatefunction faultListener(event:FaultEvent):void {
                Alert.show(event.fault.message, "Error");
           }
]]>
</mx:Script>

Finally, you need to create the ActionScript Value Object that will act as a data model for the data sent from PHP. Right-click on the src folder from Flex Navigator, and choose New > ActionScript class. For the package type org.corlan, and for the name type VOAuthor. Click OK. Now it is time to add the members and some meta-data:

package org.corlan {
     [RemoteClass(alias="VOAuthor")]
     [Bindable]
     publicclass VOAuthor {
           publicvar id_aut:int;
           publicvar fname_aut:String;
           publicvar lname_aut:String;
     }
}

The RemoteClass meta-data is very important. This tells to the ActionScript that the remote class (the one from PHP) that it maps to is called VOAuthor. If you forget this or you misconfigure it, you will get generic objects in ActionScript instead of VOAuthor, and associative arrays in PHP instead of VOAuthor.

You are done. There shouldn’t be any errors.

Now you are ready to test the code. Start the Flex application by clicking Run in the toolbar. When the application opens in your default browser, click the Get data button. You should see the data grid populated with some data:

Testing the application

To edit the items, just double click on any name and change something. When you finish editing, click outside the data grid. The changes will be sent to the server. If you don’t believe me, just go to the database and view the records.

Editing a cell

That’s it people!

190 thoughts on “Flex and PHP: remoting with Zend AMF

  1. Pingback: Flex Remoting with ZendAMF/MySQL – An Introduction Tutorial : : Jonathan Paek

  2. Thank You for the tutorial and thanx for resolving problem with Channel Disconnected. Your’ve really saved my time!

  3. Pingback: Flex and PHP webinar goodies : Mihai CORLAN

  4. Hi Mihai, i found this error in my raw view in flash builder:

    Fatal error: Call to a member function getInvokeArguments() on a non-object in C:\wamp\www\testeAGT\ZendFramework\library\Zend\Amf\Server.php on line 328

    I don’t understand this error. I have build my own services, with data typed objects. When i try this in php it works just fine as it return an array of valueObjects.

    Thank’s in advance

    Philippe

  5. @Philippe Macedo

    Try to debug the code when it gets called from the Flex side. This will be the fastest way to find what’s wrong. Basically this error appears when a variable is NULL and some code calls methods on this variable. You can read about debugging: http://corlan.org/debugging-flex-and-php/

  6. Pingback: Polish Adobe Flex User Group » Blog Archive » Testowa konfiguracja Zend AMF w kilku krokach

  7. I had the error “NetConnection.Call.BadVersion”. The solution is checking spaces after the end of the PHP script, after “?>”. Erasing the spaces passed OK. It must check it the spaces before and after “<?php" too.

  8. buenas noches alguien que me pueda indicar donde inscribirme para aprender mas de flex builder 3
    + php + mysql utilizando procedures

    jhamer anderson rojas silva

  9. Dear Corlan,

    Flash builder keep having the can’t open file error with -services “e:/air/projects/flex_php/services_config.xml”

    do you have any idea why?

  10. Hi Mihai,

    first of all great article, thanks for that.

    Although implementing this example as described in zends documentation http://framework.zend.com/wiki/display/ZFPROP/Zend_Amf+-+Wade+Arnold i got the same problem as boris described in his post

    125. Boris on September 9th, 2009 11:20 am

    Are there any news on that, you know about? Or do you have any clue what the reason for this problem could be? I am using flex4 and zend 1.10.8

    thanks
    Robert

  11. Hello ,

    i have some problem in connecting a local swf (saved on any computer) to an online database. What i did is a software for a university election, what i want is to let the local swf connect to my online database , my database is created on my online server and not on my localhost. I;m using flex 4 or adobe flash builder. Thank you. What should i do to succeed in the connection.

    Thank you ,

  12. i want the swf locally coz i don’t want to let anyone access this swf. this why i’m using it as locally ,,,,,,

    Any help

  13. Hi all,

    After almost a month of research on this problem. I was able to remove all my errors on production but still not able to retrieve data.
    Please help……
    Here is the scenerio details :
    I created a form which will take input from user and store those values in database.you can visit it here on web server :
    http://www.accurateoptics.in/
    I followed all the steps mentioned on adobe’s test drive tutorial. It works fine on my local machine with WAMP installation. But the moment,
    I upload it…Bang!!!! there comes the error ::::
    Channel.Connect.Failed error NetConnection.Call.BadVersion: : url: http://www.accurateoptics.in/gateway.php
    I googled again, read a lot of articles and figured out that some changes are required in my gateway.php and amf-config.ini files for production environment.

    Did the necessary changes and uploaded again.Bang!!!!!!! new error comes:::::::
    Channel disconnected before an acknowledgement was received.

    to solve this, again googled a lot and figured out that i need to upload zend framework files so, did that and setting up the path for zend in amf-config.ini.
    uploaded again.

    Now, i am stuck at this point where no error is thrown only an alert box comes with a ok button. also, data is not retrieved from server.

    I dont know what error i am making.here is my amf-config.ini file:::::::::::::

    [zend]
    ;set the absolute location path of webroot directory, example:
    ;Windows: C:\apache\www
    ;MAC/UNIX: /user/apache/www
    webroot =http://www.accurateoptics.in
    ;set the absolute location path of zend installation directory, example:
    ;Windows: C:\apache\PHPFrameworks\ZendFramework\library
    ;MAC/UNIX: /user/apache/PHPFrameworks/ZendFramework/library
    ;zend_path =
    zend_path = ./ZendFramework/library
    [zendamf]
    amf.production = true
    amf.directories[]= http://www.accurateoptics.in/services

    here is my gatway.php file :::::::::::::;

    false), true);
    $default_config->merge(new Zend_Config_Ini($configfile, ‘zendamf’));
    $default_config->setReadOnly();
    $amf = $default_config->amf;
    // Store configuration in the registry
    Zend_Registry::set(“amf-config”, $amf);
    // Initialize AMF Server
    $server = new Zend_Amf_Server();
    $server->setProduction($amf->production);
    if(isset($amf->directories)) {
    $dirs = $amf->directories->toArray();
    foreach($dirs as $dir) {
    // get the first character of the path.
    // If it does not start with slash then it implies that the path is relative to webroot. Else it will be treated as absolute path
    $length = strlen($dir);
    $firstChar = $dir;
    if($length >= 1)
    $firstChar = $dir[0];
    if($firstChar != “/”){
    // if the directory is ./ path then we add the webroot only.
    if($dir == “./”){
    $server->addDirectory($webroot);
    }else{
    $tempPath = $webroot . “/” . $dir;
    $server->addDirectory($tempPath);
    }
    }else{
    $server->addDirectory($dir);
    }
    }
    }
    // Initialize introspector for non-production
    if(!$amf->production) {
    $server->setClass(‘Zend_Amf_Adobe_Introspector’, ”, array(“config” => $default_config, “server” => $server));
    $server->setClass(‘Zend_Amf_Adobe_DbInspector’, ”, array(“config” => $default_config, “server” => $server));
    }
    // Handle request
    echo $server->handle();
    ?>

    Looking for some help!! Thanks in advance…………..

  14. Could you please explain how changing of data from other instance would affect synchronization with current one. What should be done?
    Thx

  15. @Alex

    If you are talking about the situation where two or more users edit the same piece of information and then submit to the server, then you handle it the same way you would do with normal HTML client: you deal on the server.

    The alternative is pretty complicated:
    - the user edits some data and then triggers the saving action
    - before sending the data to server you make a request and check what are current data on the server
    - you compare the data you got with what the user started with
    - if differences you alert the user and give him an option to change and then save

  16. Pingback: Flex and PHP Architecture « PHP Mysql Script

  17. Pingback: 1 million row DataGrid: ZendAMF, Flex data-paging, MySql « Bishop on Development

  18. Pingback: 火柴工作坊 » Streamlining Flex and PHP development with Flash Builder for PHP

  19. you don’t need third party plugins to go from flex to php … AS3′s URLloader works fine.

  20. Pingback: The architecture of Flex and PHP applications

  21. 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

  22. Pingback: blog.to-fuse» Blog Archive » Flex 3 and Zend_Amf Class Mapping Hint

  23. I’m trying for first time Flash Builder 4.5, Mysql and ZendAmf 1.10.6 installed on Linux server from two weeks ago, and all the new projects I run, always I get the “NetConnection.Call.BadVersion” error when connect to ZendAmf.

    But, last two months ago I was trying Flash Builder 4.0, MySQL and ZendAmf on the same Linux server and all my example projects was running perfectly. But that reason I’ve exported all projects from 4.0 to 4.5 and It was astohis, all run well.

    The classes are the same en both versions and the databases are the same too only because they are in the Linux server I’ve changes the versión.

    But if I create a new project on Flash Builder 4.5 using the same classes and database that are in the Linux server and server-config.xml of the older projects always I have the “NetConnection.Call.BadVersion” error.

    I want to know why is the reason? and Why new projects in 4.5 always I have the error “NetConnection.Call.BadVersion” perhaps is a bug?

  24. Hi Mihai,
    Thanks for the tutorial. I got the instances of php classes in flex as an actionscript class.

    My question is; is it possible to create the instances of actionscript classes as a php class?
    For example, i have a Login.as which created in flex. Can i send the Login.as to the php as an Object and create the same php class?

    Thanks

  25. Hi to everyone,
    I have a website hosted on Bluehost.com at a very good price. I would like to use the Zend Framework, I uploaded it, I modified all the includes to php.ini but I cannot restart the server, and when I execute the Flex script I have an error message saying: Channel disconnected
    Channel disconnected before an acknowledgement was received. Any solution to this?

  26. does the uri end at zendamf_remote/ as a folder or should it point to one of the php files in that folder?

  27. In case that can help anyone, I’ve been trying to get rid of the “NetConnection.Call.BadVersion” error for two days now and after a LOT of testing and debugging, I found out that my problem was with the encoding of my services PHP file being in UTF-8 while the gateway file was ANSI.

    I’m sure there are many different possible causes for this error but check the encoding of your files if you encounter that error!

  28. Pingback: smartcode | smartcode | blog | flex 3 and zend_amf class mapping hint

  29. Pingback: smartcode | smartcode | blog | flex 3 and zend_amf class mapping hint

  30. Pingback: smartcode | smartcode | blog | flex 3 and zend_amf class mapping hint

  31. hi Mihai,

    thanks for the tutorial btw I have some question I am trying to figure out ur tutorial on WAMP / Win7 with FB 4.5

    and I am getting a timer in my cursor it keeps on ticking nothing is retrieved !

    pls advice

  32. Special characters are not returned as expected. Is there a way to make sure zendAmf encodes with utf-8?

  33. Pingback: FLEX & PHP Architecture « PHP TALENT GURU

  34. Hi,

    Tutorial is awesome.
    Its working fine on my end but my question is what about the data security.
    As gateway path is openly visible and can be seen in the browser console also.

    Also as a SWF is client side, so anyone can be decompile it and can see the Service and methods names to send & retrieves data. My question is about data security.

    Recently we did a contest project using the same functionality. I was registering users, getting authentications and submission using same channel.

    But during the contest we found that there were entries made in the database using loop.

    As during development, i can easily communicate with database from Flash, so anyone a developer can do the same.

    Please let me know about the security part of the AMFPHP communications.

    Thanks & Regards
    Virender Kumar Jangra

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>