Adobe Flex with a PHP back-end

You would like to create a great Adobe Flex application which will show data from a MySQL-database. You Googled around and found BlazeDS would be a great back-end solution for retrieving your data. Bad luck, you have no Java experience and/or your hosting provider is not equipped to allow you to deploy BlazeDS at a Tomcat server…

What are your options now? Create the complete back-end solution yourself? Implement a webservice? Simply do some HTTP-posts/gets sending your data as comma separated values? Possible.. but there is another neat solution: ZendAMF. For this PHP-knowledge and a server running the PHP(5) module with ZendAMF is required.

In this article I describe how you can create a simple Flex application which is communicating with the back-end database using ZendAMF.

Install ZendAMF

When ZendAMF is not available yet, and you do have administrator rights at the server, install ZendAMF now.

  1. Download ZendAMF from: http://framework.zend.com/download/amf
  2. Follow installation instructions in INSTALL.txt which is included in the download.

Create the database

Create a MySQL-database design in data definition language (design.ddl).

DROP TABLE IF EXISTS 'zendamf_example_data';
CREATE TABLE 'zendamf_example_data' (
  'id' int(11) NOT NULL AUTO_INCREMENT,
  'timestamp' timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  'description' VARCHAR(255) DEFAULT NULL,
  'value' INT(11) DEFAULT NULL,
  PRIMARY KEY ('id'),
  UNIQUE KEY 'id' ('id')
);

Now create this table in the zendamf_example database.

When this database does not exist yet, excecute this command as root user in MySQL:

create database zendamf_example;

From the (bash) prompt, enter this command:

cat design.ddl | mysql -u root -p zendamf_example

Generate a test set

Fill the database with some random data. To accomplish this, we create a stored procedure.

First we remove the stored procedure when it exists already. Next we change the MySQL command delimiter from ; to // Reason for this is that we would like to use ; in the procedure itself. When we don’t change the delimiter, the definition of the stored procedure will stop after the first time we use ;

After defining the stored procedure, we switch the delimiter back to ;

Login into MySQL:

 mysql -u root -p zendamf_example

Execute these commands at the MySQL-prompt:

DROP PROCEDURE IF EXISTS createTestSet;

delimiter //

CREATE PROCEDURE createTestSet(n INT)
BEGIN
SET @i = 0;
REPEAT
SET @i = @i + 1;
INSERT INTO zendamf_example_data (value) VALUES (ROUND(RAND()*10));
UNTIL @i > n END REPEAT;
END
//

delimiter ;

The procedure will insert n-rows into the table zendamf_example_data. The ‘id’ is an auto incremented field, so we don’t supply a value. The ‘timestamp’¬† of a row will be updated automatically every time we change the row. ‘Description’ is a text-field which we don’t fill at this moment. The field ‘value’ is filled with a random numeric value in the range 0 to 10.

Now call this procedure to create a test set of 20 records by executing the command:

CALL createTestSet(20);

Tip: To list the stored procedures, execute the command:

SHOW PROCEDURE STATUS;

Now you can leave MySQL again (quit;)

Setup your development environment

Create a project directory: zendamf_example

Step into this directory (cd zendamf_example)

Create these (sub) directories:

  • server (this directory must be accessible for your HTTP-server)
  • client
  • client/net/videgro/dto

Create the value objects

In this step you create two value objects which will hold the data retrieved from the database. One for the PHP-back-end and one for the Flex-front-end. Later, in the file server/endpoint.php, mapping is performed between these two objects.

PHP Value Object Data (server/VOData.php)

Create a simple PHP-class called VOData with four fields which are not strong typed.

<?php
class VOData {
        public $id;
        public $timestamp;
        public $description;
        public $value;
}
?>

Flex/Actionscript Value Object Data (client/net/videgro/dto/VOData.as)

The actionscript class has strong typing. Casting to these types is done automagicly. The RemoteClass defines the back-end class.

package net.videgro.dto {
     [RemoteClass(alias="VOData")]
     [Bindable]
     public class VOData {
        public var id:int;
        public var timestamp:String;
        public var description:String;
        public var value:Number;
     }
}

Create the AMF endpoint (server/endpoint.php)

A small file where a AMF-server is created, a Service-class for this server is configured, a mapping is made between the PHP- and Actionscript- value objects and finally a handle to this server is returned.

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

$server = new Zend_Amf_Server();
$server->setClass("Service");

//Mapping the ActionScript VO to the PHP VO
$server->setClassMap("VOData", "VOData");

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

Implement the back end-service (server/Service.php)

In this class all magic is done. It consists of two methods which are exposed by the AMF-server. The methods are: getData, which will return all data of the table zendamf_example_data as an array.

The second method is called setData and could be used to set the value of a specific row of the table zendamf_example_data, identified by ‘id’. This method will return a string containing the result of the operation.

<?php
require_once('VOData.php');

define("DATABASE_SERVER", "localhost");
define("DATABASE_USERNAME", "root");
define("DATABASE_PASSWORD", "root");
define("DATABASE_NAME", "zendamf_example");

class Service {

 public function getData() {
        $mysql = mysql_connect(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD);
        mysql_select_db(DATABASE_NAME);

        $query = "SELECT * FROM zendamf_example_data";
        $result = mysql_query($query);

        $ret = array();
        while ($row = mysql_fetch_object($result)) {
                $tmp = new VOData();

                $tmp->id=$row->id;
                $tmp->timestamp=$row->timestamp;
                $tmp->description=$row->description;
                $tmp->value=$row->value; 

                $ret[] = $tmp;
        }
        mysql_free_result($result);
        return $ret;
 }

 public function setData($id,$value) {
  $result="Data saved.";

  $mysql = mysql_connect(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD);
  mysql_select_db(DATABASE_NAME);

  if ($id!="" && $value!=""){
   $query  = "UPDATE zendamf_example_data SET value='".$value."'";
   $query .= ",description='Set via Flex front-end.'";
   $query .= " WHERE id='".$id."'";
   mysql_query($query);
  } else {
   $result="ERROR: Check ID and Value.";
  }
  return $result;
 }
}
?>

Create a Flex services definition file (client/services-config.xml)

In this file we define a channel for the communication between the Flex front-end and the AMF-server. Ofcourse we also have to specify at which URI the AMF-endpoint could be found.

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
 <services>
   <service id="amfphp-flashremoting-service"
            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">
  <endpoint uri="http://[URL of your server]/zendamf_example/server/endpoint.php"/>
 </channel-definition>
 <channel-definition id="my-secure-zend">
  <endpoint uri="https://[URL of your server]/zendamf_example/server/endpoint.php"/>
 </channel-definition>
 </channels>
</services-config>

Create Flex-application (client/ZendAmfHelloWorld.mxml)

The complete Adobe Flex application. Main parts of the application are:

  • Declaration of RemoteObject which is pointing to the service defined in services-config.xml
  • Result listeners which handle results (data/faults/messages) returned from the remote service. Respectively update the data array, show an alert box with error or other message.
  • Datagrid and LineChart displaying the data.
  • An input field to update values.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
 xmlns:s="library://ns.adobe.com/flex/spark"
 xmlns:mx="library://ns.adobe.com/flex/mx"
 xmlns:videgro="net.videgro.*"
 backgroundColor="#FFFFFF"
 width="600"
 height="500">

 <fx:Script>
  <![CDATA[
  import net.videgro.dto.VOData;
  import mx.controls.Alert;
  import mx.rpc.events.FaultEvent;
  import mx.rpc.events.ResultEvent;
  import mx.collections.ArrayCollection;

  [Bindable] private var _data:Array;

  /**
  * Result listener for get data operation
  */
  private function getDataListener(event:ResultEvent):void {
   _data = event.result as Array;
  }

  /**
  * Result listener for set data operation
  */
  private function setDataListener(event:ResultEvent):void {
   var result:String = event.result as String;
   Alert.show("Result of setting data: "+result);
   myRemote.getData();
  }

  /**
  * Fault listener for RemoteObject
  */
  private function faultListener(event:FaultEvent):void {
   Alert.show(event.fault.message, "Error");
  }
  ]]>

 </fx:Script>

 <fx:Declarations>
  <mx:RemoteObject id="myRemote" destination="zend" source="Service"
        showBusyCursor="true" fault="faultListener(event)">
   <mx:method name="getData" result="getDataListener(event)"/>
   <mx:method name="setData" result="setDataListener(event)"/>
  </mx:RemoteObject>
 </fx:Declarations>

 <mx:VBox top="10" left="10" width="100%" height="100%">
  <mx:HBox>
   <s:Label text="1. Click the button: 'Get data'."/>
   <s:Button label="Get data" click="{myRemote.getData()}" />
  </mx:HBox>

  <s:Label text="2. Now select a row in the datagrid and enter a new value in the Value-field."/>
  <s:Label text="3. Next click 'Replace value'."/>

  <mx:HBox>
   <mx:DataGrid id="myGrid" editable="false" dataProvider="{_data}"
                itemClick="idTextInput.text=myGrid.selectedItem.id;"/>

   <mx:Form width="100%">

    <mx:FormItem label="ID">
     <s:TextInput id="idTextInput" enabled="false" width="100"/>
    </mx:FormItem>

    <mx:FormItem label="Value">
     <s:TextInput id="valueTextInput" restrict="0-9" width="100"/>
    </mx:FormItem>

    <s:Button label="Replace value"
              click="{myRemote.setData(idTextInput.text,valueTextInput.text)}" />
   </mx:Form>
  </mx:HBox>

  <mx:LineChart id="linechart" height="100%" width="70%" showDataTips="true" dataProvider="{_data}">

   <mx:horizontalAxis>
    <mx:CategoryAxis categoryField="id"/>
   </mx:horizontalAxis>

   <mx:series>
    <mx:LineSeries yField="value" form="curve" displayName="Value"/>
   </mx:series>

  </mx:LineChart>

  <mx:Legend dataProvider="{linechart}"/>

  </mx:VBox>

</s:Application>

Build Flex-application

Compile the application using Adobe Flex 4 SDK. Now step into the ‘client’-directory (cd client).

mxmlc  -services "services-config.xml" ZendAmfHelloWorld.mxml

Copy the resulting file: ZendAmfHelloWorld.swf to the directory: ‘server’ (cp ZendAmfHelloWorld.swf ../server/).

Now check the permissions of the four files (endpoint.php Service.php VOData.php ZendAmfHelloWorld.swf) in the directory ‘server’, they must be readable by the webserver daemon.

Test your application

Open the location: http://[URL of your server]/zendamf_example/server/ZendAmfHelloWorld.swf in your favourite browser, for example Mozilla Firefox. In the browser, the Flash-player plugin must be available. To check whether the Flash plugin is available and which version of the plugin is installed, type about:plugins (without http://) in the location bar of Firefox.

Click the ‘Get data’ button to perform a back-end call which will load the data from the database.

Now select a row in the datagrid and type a new value in the Value-field. Next press the button ‘Replace value’. Check the ‘description’-field in the datagrid. It should state at the selected row: ‘Set via Flex front end’ and the new value.

Make communication more secure

To make the communication between front- and back-end more secure, use a HTTPS/SSL server instead of plain unencrypted HTTP.

Open the file services-config.xml in the client-directory. Find the line: <channel ref=”my-zend”/> and replace it with <channel ref=”my-secure-zend”/> Also remove the channel-definition¬† for ‘my-zend’.

Check the endpoint URI of the channel definition for my-secure-zend. It must be starting with https and not http.

Now recompile ZendAmfHelloWorld and put it again in the ‘server’-directory.

Access ZendAmfHelloWorld.swf by typing the address https://[URL of your server]/zendamf_example/server/ZendAmfHelloWorld.swf in your browser. So httpS instead of http. Ofcourse you need to setup a HTTPS-server before you try this. Sadly the configuration of a HTTP-server is out of scope of this article.

Read more