Categories
Articles

ZEND AMF (Action Message Format) Minimalist Example Using RemoteObject and MySQL

I always liked and appreciated the AMFPHP implementation of Adobe Action Message Format. I have seen it implemented in some robust applications and it held its own. Among its best features is the browser which is very handy for testing. However Adobe has now gotten involved with Zend AMF and this is a quick shot at putting it together using the RemoteObject class.

The first example I looked at is from Lee Brimelow’s Introduction to ZendAMF at gotoAndLearn.com. This is a great example and worth the watch to get a rounded view of using Zend AMF. He uses NetConnection and Responder classes in Flash CS4. You can use his example in Flex and Air.

Adobe Flash Builder 4 and Flex 4 - Essential Training
Learn More About Flex 4

Eventually I put Zend AMF to practical use by revamping DefineIt.com to use it along with the Parsley framework.

Flash CS4 does not have a native RemoteObject class, so that leads us to using Flex.

Download files
You can build this with the free Flex SDK by using the code in the src folder. This example was built with Flex 4.

The following uses the code from the Flex example, but other than the Application and WindowedApplication tags the code is the same.

Application Class – ZendAMFRemoteObjectGetTable_Flex
Rather than use a services-config.xml file linked to the compiler line, I choose to include the channel information in the MXML. More on this method is detailed by Chris Callendar’s post Using Flex and AMFPHP without a services-config.xml file.

<?xml version="1.0" encoding="utf-8"?>
<!--
Demonstrates use of the RemoteObject to communicate with Zend AMF.
<p>Author: Lon Hosford https://www.lonhosford.com 908 996 3773</p>
-->
<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" minWidth="955" minHeight="600">
	<fx:Declarations>
		<!-- Alternative to services-config.xml added to the compiler option services -->
		<s:ChannelSet id = "zend_amf_channel_set">
			<s:channels>
				<s:AMFChannel uri="{GATEWAY_URL}"/>
			</s:channels>
		</s:ChannelSet>
		<!-- MembershipService RemoteObject -->
		<s:RemoteObject	id="members_ro"
						destination="zend-amf"
						source="MembershipService"
						channelSet="{zend_amf_channel_set}"
						showBusyCursor="true"
						fault="membersError(event)">
			<s:method name="getAllMembers" result="getAllMembersResult(event)"/>
		</s:RemoteObject>
	</fx:Declarations>

[ad name=”Google Adsense”]
On line 33 you need to set the GATEWAY_URL to match your configuration. You might want to use a default file name such as index.php for the gateway script so you only need a path to the folder.

	<fx:Script>
		<!&#91;CDATA&#91;
			import mx.controls.Alert;
			import mx.events.FlexEvent;
			import mx.rpc.events.FaultEvent;
			import mx.rpc.events.ResultEvent;
			// &#91;ADD YOUR HTTP URL TO THE GATEWAY PHP SCRIPT&#93;
			private const GATEWAY_URL:String = "http://YOUR_DOMAIN/PATH_TO_GATEWAY SCRIPT/";
			/**
			 * Member value object. Not used in this example. Included for information only.
			 * Use this to convert the data received. In this example the DataGrid dataprovider
			 * property converted the incoming amf array to an ArrayCollection and we mapped
			 * using the DataGridColumn dataField property.
			 * */
			private var memberData:MemberData;
			&#91;Bindable&#93;
			private var lang_title:String = "Minimalist Zend AMF Example Using RemoteObject";
			/**
			 * Invoke RemoteObject members_ro getAllMembers method.
			 * */
			protected function getAllMembers():void
			{
				members_ro.getAllMembers();
			}
			/**
			 * Empties the member_dg DataGrid.
			 * */
			protected function clearDataGrid():void
			{
				member_dg.dataProvider = {};
			}
			/**
			 * RemoteObject members_ro ResultEvent handler for the remote getAllMembers method.
			 * <p>Data arrives as an array and dataProvider property converts to ArrayCollection.
			 * The member_dg DataGrid contains DataGridColumn to match the expected field using
			 * the dataField property.</p>
			 * */
			protected function getAllMembersResult(e:ResultEvent):void
			{
				member_dg.dataProvider = e.result; // ResultEvent result property is an array
			}
			/**
			 * RemoteObject members_ro default FaultEvent handler.
			 * */
			protected function membersError(e:FaultEvent):void
			{
				Alert.show(e.toString());
			}
		&#93;&#93;>
	</fx:Script>

[ad name=”Google Adsense”]
This is the UI. The member_dg DataGrid contains DataGridColumn to match the expected fields in the MemberData object using the dataField property. This is a one to one relationship, however you may find times where you need to construct your own fields such as a concatenation of first and last name and thus populate the DataGrid with your own collection object.

	<s:VGroup horizontalAlign="center" width="100%" paddingTop="25">
		<s:Label text="{lang_title}" fontSize="20"/>

		<mx:DataGrid  id="member_dg"  height="100">
			<mx:columns>
				<mx:DataGridColumn headerText="Member Key" dataField="memberKey" width = "100"/>
				<mx:DataGridColumn headerText="First Name" dataField="firstName"/>
				<mx:DataGridColumn headerText="Last Name" dataField="lastName"/>
				<mx:DataGridColumn headerText="Email Address" dataField="emailAddress" width="200"/>
			</mx:columns>
		</mx:DataGrid>
		<s:HGroup>
			<s:Button label="Get Members" click="{getAllMembers();}"/>
			<s:Button label="Clear" click="{clearDataGrid();}"/>
		</s:HGroup>
	</s:VGroup>

</s:Application>

MemberData Value Object Class for Actionscript
This is the value object on the client side to define the field names for a member object. This is mapped to the server side on line 37 of the Zend AMF gateway script.

package
{
	/**
	 * Value object defining the member data
	 * */
	[RemoteClass(alias="MemberData")]
	[Bindable]
	public class MemberData
	{
		public var memberKey:uint;
		public var firstName:String;
		public var lastName:String;
		public var emailAddress:String;
	}
}

Zend AMF Gateway PHP Script
This is the gateway program for the Zend Amf. This is the file that is referenced on line 13 of the MXML file. In this example it was named index.php.

<?php
/**
*  Sample Zend AMF gateway
*  @return Endpoint &#91;Zend Amf Endpoint&#93;
* */

// Configurable values
// Debugging values
$debug = true;                             // Debugging status
if ($debug)
{
	// Report all errors, warnings, interoperability and compatibility
	error_reporting(E_ALL|E_STRICT);
	// Show errors with output
	ini_set("display_errors", "on");
}
else
{
	error_reporting(0);
	ini_set("display_errors", "off");
}
// Add the Zend AMF installation folder to the include path.
// In this example the frameworks folder is a sibling folder to
// this application folder. The frameworks folder contains the Zend
// folder that is extracted from http://framework.zend.com/download/amf
ini_set("include_path", ini_get("include_path") . PATH_SEPARATOR . "..\\frameworks" );

// Instantiate the Zend Amf server
require_once 'Zend/Amf/Server.php';
$server = new Zend_Amf_Server();

// Register your service classes
require_once 'MembershipService.php';
$server->setClass("MembershipService");

//Map ActionScript value objects to the PHP value objects.
$server->setClassMap("MemberData", "MemberData");

// Return the handle.
echo ($server->handle());

?>

[ad name=”Google Adsense”]
MembershipService Class
This is the service class that contains remote methods. Generally a the database and business logic is delegated to another API you write. In this case they are all together for simplicity.

<?php
/**
*	Service class exposing the methods to deal with membership.
*   This example includes business logic for simplicity of study.
*/
require_once 'MemberData.php';
class MembershipService
{
	public function MembershipService()
	{
		// Connect to MySql database.
		// Supply your own MySQL access values.
		// These are defaults when running on your own private computer.
		mysql_connect("localhost", "root", "");
		// Select the database.
		// Supply your own database name.
		mysql_select_db("test");
	}
	/**
	*	Get all members and all fields.
	*/
	public function getAllMembers()
	{
		// Array of MemberData objects.
		$members = array();
		// Selecting all fields and all records from table.
		// Supply your own table name and optionally your own SQL statement.
		$result = mysql_query("SELECT * FROM zend_amf_members");
		// Assuming mysql_query success. Slog through records.
		while ($row = mysql_fetch_assoc($result))
		{
			// Create a MemberData value object and populate.
			$member = new MemberData();
			$member->memberKey = $row["memberKey"];
			$member->firstName = $row["firstName"];
			$member->lastName = $row["lastName"];
			$member->emailAddress = $row["emailAddress"];
			array_push($members, $member);
		}
		// Return the members array to client.
		return $members;
	}
}
?>

MemberData Value Object Class for PHP
This is the value object on the server side to define the field names for a member object. This is mapped to the client side on line 37 of the Zend AMF gateway script.

<?php
/**
 * Value object defining the member data
 * */
class MemberData
{
  public $memberKey;	// uint
  public $firstName;	// String
  public $lastName;		// String
  public $emailAddress;	// String
}
?>

SQL To Create Testing Table
The PHP script uses zend_amf_members for the table and this is the SQL to create that table. In this example the database was called test.

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";

--
-- Database: `test`
--

-- --------------------------------------------------------

--
-- Table structure for table `zend_amf_members`
--

CREATE TABLE IF NOT EXISTS `zend_amf_members` (
  `memberKey` int(10) unsigned NOT NULL auto_increment,
  `firstName` varchar(30) NOT NULL default '',
  `lastName` varchar(30) NOT NULL default '',
  `emailAddress` varchar(50) NOT NULL default '',
  PRIMARY KEY  (`memberKey`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='Zend Amf Examples' AUTO_INCREMENT=7 ;

--
-- Dumping data for table `zend_amf_members`
--

INSERT INTO `zend_amf_members` (`memberKey`, `firstName`, `lastName`, `emailAddress`) VALUES
(1, 'Donald', 'Duck', 'quacker@pond.com'),
(2, 'Daffy', 'Duck', 'daft_2x@farm.org'),
(3, 'Elmer', 'Fudd', 'elmer.fudd@hunters.net'),
(4, 'Bugs', 'Bunny', 'whats_up_doc@underground.org'),
(5, 'Yosemite', 'Sam', 'varmint_chaser@forest.com'),
(6, 'Wile', 'Coyote', 'ceo@acme.com');