Get to know Flex and Zend_Amf

By Jack Herrington

The best applications for the rich Internet connect to the server to both read and write data. So, the easier it is to get data from the server and send data back to it, the quicker you can develop some awesome applications.

Reading and posting to Extensible Markup Language (XML)–based services with Adobe Flex is easy. But what’s even easier is using Adobe’s Action Message Format (AMF) to send messages between the Flex client and the server. It’s as easy to use as a method call on an object.

This article shows how to set up an AMF service on your PHP web server using the Zend Framework and Zend_Amf.

Getting set up

Setup starts with installing the Zend Framework on your PHP installation—in other words, downloading the Zend Framework, then installing it in a recommended location for your operating system. Finally, you add the Zend directory to the include path within the php.ini file. You may have to reboot your web server after this so that the changes are picked up. The Zend_Amf extension comes with the Zend Framework nowadays, so you don’t have to worry about installing it yourself.

From there, the next step is to prepare the database. In case you missed it, here’s a refresher:

  1. Create the contacts database with the mysqladmin command:
    % mysqladmin -user=root create contacts
    
  2. You may have to add a password or change the root user to something else, depending on how you have set up your MySQL database.
  3. Add the contact table into the database using the Structured Query Language (SQL) code below:
    DROP TABLE IF EXISTS contact;
    CREATE TABLE contact ( name TEXT, email TEXT, phone TEXT );
    

    If the SQL code is in a file called contact.sql, the entire process of initializing the database would look like this on the command line:

    % mysqladmin -user=root create contacts
    % mysql -user=root contacts < contact.sql

With the Zend Framework installed and the database created, it’s time to build the first version of the AMF server.

Creating a read-only service

The first version of the PHP AMF service, named Contacts, is going to be read only. It simply returns the list of contacts in the database. The second version will have another method to add a contact to the database.

You need two files for an AMF server. The first is the “endpoint” page—that is, the page for the AMF server. In this setup, the file is named index.php and placed in the root of the web server. The contents of index.php are shown in Listing 1.

Listing 1. Index.php

setClass( "Contacts" );

echo( $server->handle() );
?>

This file remains the same in both versions of the Contacts server. Basically, you are creating an AMF server class, registering any PHP classes you want to provide as AMF services, then letting the server class do the rest by calling the handle() method. If it gets much easier than that, I don’t know how.

The Contacts class does the heavy work of actually implementing the service. The Contacts class is contained in the Contacts.php file, which is shown in Listing 2.

Listing 2. Contacts.php

<?php
class Contacts
{
    public function Contacts() {
    }

    public function getContacts()
    {
        $contacts = array();

        mysql_connect( 'localhost', 'root', '' );
        mysql_select_db( 'contacts' );

        $res = mysql_query( 'SELECT * FROM contact' );
        while( $contact = mysql_fetch_assoc($res) ) {
            $contacts []= $contact;
        }

        return $contacts;
    }
}
?>

One of the great things about Zend_Amf is that the classes you register as services are really just regular old PHP classes. In this case, it’s a class that has a single method—getContacts()—that queries the database and returns an array of contacts. You could use this class within your regular PHP server code completely unchanged. How cool is that?

To test it, just use a web browser—in this case Apple Safari—to browse to the index.php page. You can see the result in Figure 1.

Just making sure the end point works

Figure 1. Just making sure the end point works

If everything is set up properly, you will see this simple “Zend Amf Endpoint” message. If not, you will need to work through any issues with the server before moving on to implement the client.

Implementing the read-only client

Building the client starts with creating a Flex project. I call mine Contacts: You can call yours whatever you wish. After that, you need to add a new file to the project called services-config.xml. The contents of that file are shown in Listing 3.

Listing 3. services-config.xml

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

Note: You need to change the Uniform Resource Indicator (URI) of the endpoint page if it’s different from localhost.

With that file in the project, you now need to get its absolute location on disk. You can do that by right-clicking the services-config.xml file name, then clicking Properties. The absolute path is shown as Location, as you can see in Figure 2.

Absolute location of the services-config.xml file

Figure 2. The absolute location of the services-config.xml file

Paste that absolute file path, then open the project properties. Click the Flex Compiler tab. From there, add services and the file path to the additional compiler arguments. You can see that in Figure 3.

Setting the services location

Figure 3. Setting the services location

You’re basically telling the compiler where to find this services file, which defines where the AMF services are.

The final step in setting up the project is to set the output path to somewhere in the web server’s documents directory. I set it to a sub-directory called Hello AMF, as you can see in Figure 4, but you can put it anywhere you want.

Setting the build location

Figure 4. Setting the build location

That’s all you need to do to the project. From here, it’s just straight Flex and PHP.

The first version of the Flex application is shown in Listing 4.

Listing 4. Version 1 of the Flex application

<?xml version="1.0" encoding="utf-8"?>
<mx:Application pageTitle="Contacts" layout="absolute"
xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="onStartup()">

<mx:Script>
<![CDATA[
import mx.rpc.events.ResultEvent;

private function onStartup() : void {
    svcContacts.getContacts();
}

private function doContactsResult( event:ResultEvent ) : void {
    dg.dataProvider = event.result; 
}
]]>
</mx:Script>

<mx:RemoteObject id="svcContacts" destination="zend" source="Contacts">
<mx:method name="getContacts" result="doContactsResult( event )" />
</mx:RemoteObject>

<mx:Panel width="500" height="400" layout="vertical" title="Contacts" paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5"
horizontalAlign="center" horizontalCenter="0" verticalCenter="0">

<mx:DataGrid id="dg" width="100%" height="100%">
<mx:columns>
    <mx:DataGridColumn headerText="Name" dataField="name" />
    <mx:DataGridColumn headerText="Email" dataField="email" />
    <mx:DataGridColumn headerText="Phone" dataField="phone" />
</mx:columns>
</mx:DataGrid>

<mx:Form>
    <mx:FormItem label="Name">
        <mx:TextInput id="txtName" />
    </mx:FormItem>
    <mx:FormItem label="Email">
        <mx:TextInput id="txtEmail" />
    </mx:FormItem>
    <mx:FormItem label="Phone">
        <mx:TextInput id="txtPhone" />
    </mx:FormItem>
</mx:Form>
<mx:Button label="Add" />
</mx:Panel>
</mx:Application>

Most of the code is in the definition of the user interface (UI). A tag defines a little window-type area. Within that, a DataGrid shows the contacts. Below that is a form you’ll use to add a new contact and an Add button.

The server contact code is in the onStartup() method, which is called when the application calls the getContacts() method on the svcContacts object. The svcContacts object is the AMF proxy. The call to getContacts() is asynchronous and invokes the getContacts() method on the server.

The asynchronous method fires back to the doContactsResult() method with the results from the server. The doContactsResult() method just sets the dataProvider on the DataGrid with the result from the server.

You can see it in all its glorious action in Figure 5.

The basic form

Figure 5. The basic form

Wait a second! The DataGrid is blank? What does that mean? Oh, right: The contact table is empty. Well, you can fix that by opening MySQL and adding the following record to the table:

INSERT INTO contact VALUES ( 'Jack Herrington', 'jack@jackherrington.com', '510-555-1212' );

Now, refresh the browser. You can see the record in Figure 6.

After the first record is added

Figure 6. After the first record is added

How easy is that? Calling the server is almost as easy as calling any regular ActionScript method—although it is asynchronous.

To complete this example, you need to add the ability to add a contact to the database.

Building the read-write system

Begin by upgrading the server code with an add() method that will add a contact to the database. The upgraded code for the Contacts class is shown in Listing 5.

Listing 5. The upgraded Contacts class

<?php
class Contacts
{
    public function Contacts() {
    }

    public function add( $name, $email, $phone ) {
mysql_connect( 'localhost', 'root', '' );
mysql_select_db( 'contacts' );

$insert = sprintf( "INSERT INTO contact VALUES ( '%s', '%s', '%s')",
mysql_real_escape_string( $name ),
mysql_real_escape_string( $email ),
mysql_real_escape_string( $phone ) );

mysql_query($insert);
} public function getContacts() { $contacts = array(); mysql_connect( 'localhost', 'root', '' ); mysql_select_db( 'contacts' ); $res = mysql_query( 'SELECT * FROM contact' ); while( $contact = mysql_fetch_assoc($res) ) { $contacts []= $contact; } return $contacts; } } ?>

This new version has an add() method that creates an SQL INSERT statement with the three parameters supplied. To make it more secure, use the mysql_real_escape_string native function to ensure that you don’t pass along any SQL code that might allow for SQL injection attacks.

The final version of the Flex code that uses this new add() method is shown in Listing 6.

Listing 6. The final Flex code with the add() method

<?xml version="1.0" encoding="utf-8"?>

<mx:Application pageTitle="Contacts" layout="absolute"
xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="onStartup()">
<mx:Application
 pagetitle="Contacts" layout="absolute"
 xmlns:mx="http://www.adobe.com/2006/mxml"
 creationcomplete="onStartup()">
<mx:Script>
<![CDATA[
import mx.rpc.events.ResultEvent;

private function onStartup() : void {
    svcContacts.getContacts();
}

private function doAdd( event:Event ) : void {
    svcContacts.add( txtName.text, txtEmail.text, txtPhone.text );
    txtName.text = '';
    txtEmail.text = '';
    txtPhone.text = '';
}

private function doAddResult( event:ResultEvent ) : void {
    svcContacts.getContacts();
}

private function doContactsResult( event:ResultEvent ) : void {
    dg.dataProvider = event.result; 
}
]]>
</mx:Script>

<mx:RemoteObject id="svcContacts" destination="zend" source="Contacts">
<mx:method name="getContacts" result="doContactsResult( event )" />
<mx:method name="add" result="doAddResult( event )" />
</mx:RemoteObject>

<mx:Panel width="500" height="400" layout="vertical" title="Contacts" paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5"
horizontalAlign="center" horizontalCenter="0" verticalCenter="0">

<mx:DataGrid id="dg" width="100%" height="100%">
<mx:columns>
    <mx:DataGridColumn headerText="Name" dataField="name" />
    <mx:DataGridColumn headerText="Email" dataField="email" />
    <mx:DataGridColumn headerText="Phone" dataField="phone" />
</mx:columns>
</mx:DataGrid>

<mx:Form>
    <mx:FormItem label="Name">
        <mx:TextInput id="txtName" />
    </mx:FormItem>
    <mx:FormItem label="Email">
        <mx:TextInput id="txtEmail" />
    </mx:FormItem>
    <mx:FormItem label="Phone">
        <mx:TextInput id="txtPhone" />
    </mx:FormItem>
</mx:Form>
<mx:Button label="Add" click="doAdd( event )" />
</mx:Panel>
</mx:Application>
</mx:Application>

All you’ve done is add a new method to the tag and two new methods—doAdd() and doAddResult()—to the Flex code. The doAdd() method calls the server add() method. The doAddResult() function, which is called when the server responds, requests an update to the contacts list from the server.

Now, you can bring up the interface and enter a new contact, as shown in Figure 7.

Adding the record with the form

Figure 7. Adding the record with the form

When you click Add, the server add() method is invoked with the text from the fields, and the fields are blanked out by the doAdd() method. The doAddResult() method is then called when the server finishes. That method calls the getContacts() method on the server, which updates the list, as you can see in Figure 8.

After the recond record has been added

Figure 8. After the second record has been added

This is all it takes to create a complete reading and writing example with AMF doing the network heavy lifting between the client and the server.

Conclusion

As you can see, AMF servers are really easy both to implement on the server side and consume on the client side. In fact, the client is so easy, it’s almost like having a local method. It’s just an asynchronous method.

There is a lot more to the Zend_Amf implementation than is shown by this simple example. In particular, the Zend version of the AMF server provides for object type mapping, which means that if you have a PHP object of type Contact and a corresponding ActionScript class called Contact, Zend_Amf will do the mapping correctly and you’ll end up with real Contact objects in ActionScript code—not just basic ActionScript Object values. Perhaps in another article I’ll dig into that even further.