Ryan has posted 9 posts at DZone. View Full User Profile

Creating a basic CRUD application using Flex and PHP with Zend AMF

09.09.2010
| 15944 views |
  • submit to reddit

In Flash Builder 4 there are many new features that make it much easier to build Flex applications that work with data from a PHP back-end via a standard create, read, update, and delete (CRUD) setup. Sometimes, however it's helpful to start with the basics, so you can appreciate the new data-centric development features all that much more. This article takes you through setting up the PHP code on the server and configuring Zend AMF to work with your Flex application. Then it takes you through creating the code needed on the client side to both call service methods and handle responses from the server.

Note: Some of the best practices for using the RemoteObject class have changed from Flex 3 to Flex 4. This article uses Flex 4, so you may notice some differences if you're coming from a Flex 3 background.

Flash Builder 4
Sample files:
Prerequisite knowledge

Some knowledge of PHP and previous experience with Flex will be helpful.

 

Quick background

There are a few different ways to connect Flex and PHP together. Because Flex supports both REST-based services and SOAP web services you can consume PHP data in a Flex application using either of those two methods. But a third way Flex and PHP can exchange data is over AMF. AMF is a lightweight, binary protocol that is native to Flash Player. That means Flash Player can parse, manipulate, and render AMF data very quickly and much faster than it can parse and manipulate XML data. In order to use AMF you must have a server that supports the creation of AMF objects. PHP has a number of AMF solutions but Adobe has been working closely with Zend to add AMF support to the Zend Framework so that's what I'll use here. The Zend AMF classes in the Zend Framework will do the work of creating the AMF objects on the server so you can send them directly to the Flex application.

 

Setting up the server

Before jumping into the Flex application development, you’ll need to set up your server environment and the server-side PHP code.

Set up the web server and database

To run the example application for this tutorial, you’ll need a web server with PHP support and an SQL database. If you don’t already have access to such an environment, you can set up Apache, MySQL, and PHP together. See my article Flash Builder 4 and PHP – Part 1: Data-centric development for pointers to resources on setting this up.

That article also includes instructions on setting up the National Forests MySQL database from the sample file php_demos.sql.

Set up the server-side code

When creating a basic CRUD application there are two main PHP classes to focus on. The first is a value object (VO), which is just a PHP class that represents and stores the data from the database. In this case it's a NationalForest object with several properties of national forests. The value object will usually closely match the schema of a database table. In this example it's also going to match up exactly with a corresponding value object in the Flex application. When the data is transferred from the PHP server to the Flex application all of the properties and data types will be maintained.

The value object includes properties for each column in the database table: id, state, area, established, closest_city, and name (see Figure 1).

Figure 1. The sample application in a browser, displaying data from the National Forests table

The file NationalForest.php implements the PHP value object for this example.

NationalForest.php
<?php
class NationalForest
{
public $id;
public $state;
public $area;
public $established;
public $closest_city;
public $name;

public function __construct()
{
$this->id = 0;
$this->state = "";
$this->area = 0;
$this->established = date("c");
$this->closest_city = "";
$this->name = "";
}
}
?>

The second main PHP class is the service or Data Access Object (DAO). Since this is a CRUD example, there will be four methods: create, read, update, and delete. Each one of these methods is invoked by the Flex application to make the corresponding change to the database. The file ForestService.php implements a PHP class that contains these four methods.

ForestService.php
<?php
include 'NationalForest.php';

class ForestService
{
// Usernames and passwords
protected $host = "localhost";
protected $username = "root";
protected $password = "root";
protected $db = "php_demos";

// Connection function for accessing the database
protected function connect()
{
date_default_timezone_set("UTC");

$connection = mysql_connect($this->host,$this->username,$this->password)
or die ("Unable to connect to database.");

$db = mysql_select_db($this->db)
or die ("Unable to select database.");
}

// Create method
public function createForest(NationalForest $forest)
{
$this->connect();
$query = sprintf("insert into national_forests (state, area, established, closest_city, name)
values ('%s','%s','%s','%s','%s')",
mysql_real_escape_string($forest->state),
mysql_real_escape_string($forest->area),
mysql_real_escape_string($forest->established),
mysql_real_escape_string($forest->closest_city),
mysql_real_escape_string($forest->name));
$rs = mysql_query($query)
or die ("Unable to complete query.");

// Use the id that we just created to set the id of the original forest object.
$forest->id = mysql_insert_id();

// And return the forest object
return $forest;
}

// Read method
public function readForests()
{
$this->connect();
$rs = mysql_query("select * from national_forests")
or die ("Unable to complete query.");

$national_forests = array();

while( $row = mysql_fetch_assoc($rs) )
{
$forest = new NationalForest();
$forest->id = $row['id']+0;
$forest->state = $row['state'];
$forest->area = $row['area']+0.0;
$forest->established = new DateTime($row['established']);
$forest->closest_city = $row['closest_city'];
$forest->name = $row['name'];

array_push($national_forests,$forest);
}

return $national_forests;
}

// Update method
public function updateForest(NationalForest $forest)
{
$this->connect();
$query = sprintf("update national_forests set
state = '%s', area = '%s', established = '%s', closest_city = '%s', name = '%s'
where id = '%s' ",
mysql_real_escape_string($forest->state),
mysql_real_escape_string($forest->area),
date("c",mysql_real_escape_string($forest->established)),
mysql_real_escape_string($forest->closest_city),
mysql_real_escape_string($forest->name),
mysql_real_escape_string($forest->id));
$rs = mysql_query($query)
or die ("Unable to complete query.");

return $rs;
}

// Delete method
public function deleteForest($id)
{
$this->connect();
$query = sprintf("delete from national_forests where id = '%s'",
mysql_real_escape_string($id));
$rs = mysql_query($query)
or die ("Unable to complete query.");

return $rs;
}

}

?>

Copy ForestService.php and NationalForest.php to your web server.

 

Set up the gateway

Now that the core PHP code is ready, the next step is to expose it to your Flex application. When using Flash Remoting, Flash Player doesn't talk directly to the PHP service. Instead, everything goes through a gateway. The gateway does the work of converting objects and translating the results from the PHP methods into native ActionScript objects. Setting this up is fairly straightforward.

First, you need to have a file on your server that will act as the gateway and has access to the Zend framework because it is going to leverage the Zend_AMF classes. You can download Zend AMF as part of the Zend Framework from Zend’s website. In my setup, I have the Zend Framework, the value object file, the services, and this gateway file all in one folder on my local server but for security reasons, it's a good idea to keep the Zend Framework out of a web-accessible folder.

The contents of the gateway file are fairly simple. Basically, all this file does is set up the Zend_AMF server, map a class to it (the service class from above, ForestService.php), and create a mapping between the ActionScript value object and the PHP value object. The ActionScript VO hasn't been created yet, but it will have the same name as the PHP VO. The file has to be in a web accessible location; I named mine index.php.

After you install the Zend framework, copy the index.php sample file to your web server. If everything is set up correctly, when you browse to index.php, you should see "Zend Amf Endpoint". If you don't see that, something's wrong and you’ll need to recheck your configuration.

index.php
<?php
include 'Zend/Amf/Server.php';
include 'ForestService.php';

// Create a new instance of the Zend_Amf server
$server = new Zend_Amf_Server();

// This exposes the methods from ForestService to the Flex application
$server->setClass("ForestService");

// This maps the ActionScript type to the PHP type
// and allows us to transfer complex types between PHP and Flex
$server->setClassMap("NationalForest","NationalForest");

// The handle() statement sets it all in motion
echo($server->handle());
?>
 Set up services-config.xml

The final piece that is required is an XML file called services-config.xml. This file tells the Flex application where to find the methods that the PHP server is going to expose. Below is a basic services-config.xml that you can use. The one thing that needs to be changed is the endpoint URI of the channel-definition. This should point to the location where you exposed the gateway file (in this example, index.php). Flash Builder will use this file to create the right mappings when it compiles your Flex application. As a result, the services-config.xml file doesn't need to be in a web accessible location.

services-config.xml
<?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="zendamf">
<channels>
<channel ref="zend-amf-channel"/>
</channels>
<properties>
<source>*</source>
</properties>
</destination>
</service>
</services>
<channels>
<channel-definition id="zend-amf-channel" class="mx.messaging.channels.AMFChannel">
<endpoint uri="http://localhost:8888/zend_mamp/php_crud/" class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>
</channels>
</services-config>

That's all that is required on the server side to connect a PHP back-end and a Flex application.

 

Creating the Flex application

Note: You can follow the instructions in this section to build the application by copying and pasting code into a Flex project that you create. If you prefer, however, you can instead import the complete project by choosing File > Import Flex Project (FXP) in Flash Builder and then selecting the PHPCrud.FXP sample file.

Follow these steps to create the Flex application:

  1. Choose File > New > Flex Project.
  2. Type MyPHPCrud for the project name, and for the Application Server Type, select None/Other.
  3. Click Finish.

    In order to make a connection to PHP using Flash Remoting, Flex has to be linked to the services-config.xml file you created earlier.

  4. After the project is created, right-click the project in the Package Explorer and select Properties.
  5. Select Flex Compiler, and then add the following to the Additional Compiler Arguments settings:

    -services <absolute path to your services-config.xml file>

    For example, mine looks like this:

    -services /Applications/MAMP/htdocs/zend_mamp/php_crud/services-config.xml
  6. Click OK.

    If you mistype the absolute path, Flash Builder will throw an error so you know to fix it before running the application.


Add the declarations

To consume AMF data in a Flex application you use the RemoteObject tag. In Flex 3, you could put the RemoteObject tags anywhere in your application but in Flex 4 the rules have changed slightly. The new version of Flex has a set of <fx:Declarations> tags, which is where all of the non-visual code goes. Also, instead of using the <mx:operations> tag, you set up CallResponders that let you attach events to specific functions. So in this example I have set up a RemoteObject tag with a CallResponder tag for each operation.

Replace the <fx:Declarations> tag in your MyPHPCrud.mxml file with the following:

<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
<s:RemoteObject id="ro" destination="zendamf" source="ForestService"/>
<s:CallResponder id="createResponder" result="createResponder_resultHandler(event)" fault="ro_faultHandler(event)" />
<s:CallResponder id="readResponder" result="readResponder_resultHandler(event)" fault="ro_faultHandler(event)" />
<s:CallResponder id="updateResponder" result="updateResponder_resultHandler(event)" fault="ro_faultHandler(event)" />
<s:CallResponder id="deleteResponder" result="deleteResponder_resultHandler(event)" fault="ro_faultHandler(event)" />
</fx:Declarations>

The most important part of the RemoteObject tag is the destination property. That should match the id of the destination that was created in the services-config.xml file. RemoteObject uses that destination id and the services-config.xml file to find the correct PHP file to talk to.

I have also added event handlers to each of the CallResponder objects, which will be called when the application receives a result from the PHP class or encounters an error.

 

Add the UI elements

This application is going to show a list of NationalForests in a DataGrid component and let the user update or delete them with a form. With Flash Builder, you can create the DataGrid and other form elements via drag and drop from the Components panel in Design view or by hand coding them.

Add the following code for the DataGrid and the form directly below the <fx:Declarations> tag:

<mx:DataGrid id="dg" dataProvider="{arrNationalForests}" x="10" y="10" width="600" />
<mx:Form x="5" y="207">
<mx:FormItem label="Id">
<s:TextInput id="tiId"/>
</mx:FormItem>
<mx:FormItem label="Area">
<s:NumericStepper id="nsArea"/>
</mx:FormItem>
<mx:FormItem label="Name">
<s:TextInput id="tiName"/>
</mx:FormItem>
<mx:FormItem label="Closest City">
<s:TextInput id="tiClosestCity"/>
</mx:FormItem>
<mx:FormItem label="State">
<s:TextInput id="tiState"/>
</mx:FormItem>
<mx:FormItem label="Established">
<mx:DateField id="dfEstablished"/>
</mx:FormItem>
</mx:Form>
<s:Button id="btnCreate" x="258" y="210" label="Edit Local Data"/>
<s:Button id="btnUpdate" x="258" y="239" label="Update"/>
<s:Button id="btnDelete" x="258" y="268" label="Delete"/>
 

Create the ActionScript value object

The next step is to create the value object within the Flex project. In the previous section you created a PHP value object class with the properties of a NationalForest object. One of the benefits of using AMF to communicate between Flex and PHP is that you can map custom types across the two languages. That means the NationalForest PHP class can be mapped directly to a NationalForest ActionScript class so that you can maintain the types in your code.

In order for that to work, you need to create the NationalForest class in ActionScript:

  1. Right-click the src folder in Flash Builder and select New > Package.
  2. Type vo for the name and click Finish.
  3. Right-click the vo package and select New > ActionScript class.
  4. Type NationalForest for the name and click Finish.

That will generate the skeleton code for you and then you can fill in the properties. You will also need to add metadata to the ActionScript class that associates this class with the one from PHP. Do this by using the [RemoteClass] metadata and setting the alias property to the name of your PHP class. You will also want to use the [Bindable] metadata attribute so you can use this class as a bindable value in your application. Your class should look like this:

package vo
{
[RemoteClass(alias="NationalForest")]
[Bindable]
public class NationalForest
{
public var id:int;
public var state:String;
public var area:Number;
public var established:Date;
public var closest_city:String;
public var name:String;
}
}
 

Call the service

Now that the VO has been created you can start preparing the application to call the services from your PHP class. Back in the Flex application, create an event handler for the application's creationComplete event.

For example:

<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"
creationComplete="application1_creationCompleteHandler(event)"
xmlns:vo="vo.*"
minWidth="955" minHeight="600">

Note: The xmlns:vo="vo.*" line above declares the namespace for the value object.

Next, add the event handler below to the fx:Script block. The event handler calls the CRUD read method. To call it, you set the token property on the readResponder object to the actual service call, in this case the readForests() method on the RemoteObject component.

protected function application1_creationCompleteHandler(event:FlexEvent):void
{
readResponder.token = ro.readForests();
}

Now the result handlers need to be created for the readResponder. The CallResponder code in the <fx:Declarations> tag created the placeholders for the result and fault handlers but you need to add those to the script block. When the readForests() method gets called, the result should be set to an array that is currently bound as the data provider for the data grid. The <fx:Script> block needs that array definition as well as the result and fault handlers for the readResponder. Add the following to the <fx:Script> block above the <fx:Declarations> tag:

<fx:Script>
<![CDATA[
import flash.utils.describeType;
import flash.utils.getQualifiedClassName;

import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.events.FlexEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;

import vo.NationalForest;

[Bindable]
public var arrNationalForests:ArrayCollection = new ArrayCollection();


protected function application1_creationCompleteHandler(event:FlexEvent):void
{
readResponder.token = ro.readForests();
}

protected function readResponder_resultHandler(event:ResultEvent):void
{
arrNationalForests.source = event.result as Array;
}

]]>
</fx:Script>
 

Add support for manipulating data

Now the application can bring in data but it should also be able to manipulate data. The form tags have already been created but they are going to require a slight modification. Whenever an item is selected on the DataGrid, the form should be updated with the selected information so it can be modified. First create a NationalForest object that will be the source for the form.

In the fx:Declarations tag add the following tag:

<vo:NationalForest id="nationalForest" />

That creates a NationalForest object with the id of nationalForest. Flex 4 added the ability to do two-way data binding so that when two items are bound together, a change to either one will result in the other being updated. Two-way data binding makes it easy to let users make changes to the form and have those change the NationalForest object so it can be used to call the PHP classes. You just need to modify some of the form properties by binding them to the properties of the NationalForest object.

Update the <mx:Form> as follows:

<mx:Form x="5" y="207">
<mx:FormItem label="Id">
<s:TextInput id="tiId" text="{nationalForest.id}" enabled="false"/>
</mx:FormItem>
<mx:FormItem label="Area">
<s:NumericStepper id="nsArea" value="@{nationalForest.area}" maximum="10000000" minimum="0" stepSize="10" />
</mx:FormItem>
<mx:FormItem label="Name">
<s:TextInput id="tiName" text="@{nationalForest.name}"/>
</mx:FormItem>
<mx:FormItem label="Closest City">
<s:TextInput id="tiClosestCity" text="@{nationalForest.closest_city}"/>
</mx:FormItem>
<mx:FormItem label="State">
<s:TextInput id="tiState" text="@{nationalForest.state}"/>
</mx:FormItem>
<mx:FormItem label="Established">
<mx:DateField id="dateEstablished" selectedDate="@{nationalForest.established}"/>
</mx:FormItem>
</mx:Form>

The @ sign creates the two-way data binding. Now any change to that NationalForest object will fill the form with the correct data. To make it easy to modify data, you can add an event handler to the DataGrid component so that any item that is selected is then populated in the form.

Simply edit the DataGrid tag to change the NationalForest object to the selectedItem when something changes on the DataGrid.

<mx:DataGrid id="dg" x="10" y="10" width="600" dataProvider="{arrNationalForests}" 
change="nationalForest = dg.selectedItem as NationalForest;"/>
 

Add the remaining CRUD operations

All that's left is to add the code for the rest of the CRUD operations. The three buttons that are part of the form correspond to the remaining operations: create, update, and delete. Using the same format as above (with the token property of the callResponder) it's pretty straightforward to call the PHP functions when those buttons are clicked.

Replace the buttons with the following code:

<s:Button id="btnCreate" x="258" y="210" label="Create" click="createResponder.token = ro.createForest(nationalForest);"/>
<s:Button id="btnUpdate" x="258" y="239" label="Update" click="updateResponder.token = ro.updateForest(nationalForest);"/>
<s:Button id="btnDelete" x="258" y="268" label="Delete" click="deleteResponder.token = ro.deleteForest(nationalForest.id);"/>

Looking back at the PHP code, the create and update methods both take an object of type NationalForest. Since AMF allows for type-mapping, the NationalForest object sent from Flex will match up with the NationalForest class that the PHP methods expect. The delete method takes an id so the deleteForest method just passes in the id of the currently selected item.

The last piece of the puzzle is handling the results for these three methods. The main issue is that the data grid needs to be kept up to date whenever a record changes. Luckily, the two-way data binding takes care of most of that. This really falls under the broad category of data management. Flash Builder 4 has some great built-in tools for data management, but for simplicity in this tutorial I’ve used a very basic implementation of data management.

The responder for the createForest method adds the new forest to the array, updates the NationalForest object, and scrolls the data grid to the new entry. Add the following to the <fx:Script> block:

protected function createResponder_resultHandler(event:ResultEvent):void
{
arrNationalForests.addItem(event.result);
nationalForest = event.result as NationalForest;
dg.scrollToIndex(arrNationalForests.length);
}

The two responders for the updateForest method and the deleteForest method are straightforward. In fact the updateForest responder doesn't require any code, so I just created a trace statement. But for deletes, the data grid won't update automatically so the responder can be used to update the data grid by deleting the selected item.

Add the following two methods:

protectedfunction updateResponder_resultHandler(event:ResultEvent):void
{
trace('updated successfully');
}


protectedfunction deleteResponder_resultHandler(event:ResultEvent):void
{
arrNationalForests.removeItemAt(dg.selectedIndex);
}

And finally, you’ll need to add a fault handler:

protected function ro_faultHandler(event:FaultEvent):void
{
Alert.show(event.fault.message);
}

That’s it. You should now be able to run the application, view the data, create new records, update records, and delete records.

 

Where to go from here

Now that you’ve seen how to create a basic CRUD application that works over AMF between Flex and PHP from the ground up, you may want to learn more about how the data-centric development features in Flash Builder 4 simplify the process. You can find out more in my article Flash Builder 4 and PHP – Part 1: Data-centric development.

 

AttachmentSize
Fig1.png125.02 KB
Published at DZone with permission of its author, Ryan Stewart.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)