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.
- Download ZendAMF from: http://framework.zend.com/download/amf
- 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.