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.