By Michael Ramirez <michael_ramirez44 AT yahoo DOT com>
The following is a list of the required libraries to run this tutorial. I recommand the you download all the files before starting the tutorial.
Extract the contents of apache-ant-1.7.0-bin.zip to your hard drive. We will refer to this location as [ANT_HOME]. Make sure you add [ANT_HOME]/bin to your system path.
Extract the flexTasks.jar file from flex_ant_tasks_012307.zip into the [ANT_HOME]/lib directory.
NOTE: You must have an Adobe Account to download the Flex 2 SDK.
Extract the contents of flex_sdk_2.zip to your hard drive. We will refer to this location as [FLEX_SDK].
Choose a location on your hard drive to store your project. Create a new directory with the following directory structure. We will refer to this location as [PROJECT_HOME].
Extract the contents of the amfphp-1.9.beta.20070126.zip file into your [PROJECT_HOME]/src/php directory. We will refer to this location as [AMFPHP_HOME].
Before we can start using Amfphp we must first edit the gateway.php file located in the [AMFPHP_HOME] directory. Edit the charset setting. Select the charset appropriate for your language. In this case we will be using the English charset. Replace the original charset with the English one below.
$gateway->setCharsetHandler( "none", "ISO-8859-1", "ISO-8859-1" );
This tutorial assumes that you are fimilar with basic MySQL administration. If you need assistance with MySQL please refer to the MySQL Documentation.
For this tutorial we will be using the test database that comes pre-installed with MySQL. Login to your MySQL server and selected the test database. Execute the SQL script below to create the Employee's table.
CREATE TABLE Employee (
empId bigint AUTO_INCREMENT NOT NULL,
firstName varchar(255),
lastName varchar(255),
phone varchar(255),
email varchar(255),
title varchar(255),
PRIMARY KEY(empId),
INDEX Employee_empId_INDEX (empId));
DISCLAIMER: The php classes used in this tutorial are based off classes generated by Titantic Linux DAO Generator. Comments have been removed for readablility. The Datasource.php class is the only class generated by DAO GEN that did not need any modifications. The remaining classes generated by DAO GEN will not work with AmfPhp without modification.
Amfphp looks for your php services in the [PROJECT_HOME]/src/php/amfphp/services/ directory by default. You can modify this setting by changing the $servicesPath variable in the globals.php file under your [PROJECT_HOME]/src/php/amfphp/ directory. Methods prefixed with an underscore '_' are considered private and won't be accessible to your Flex application. We will store our php files using package style naming. Create the following directory structure under your [PROJECT_HOME]/src/php/amfphp/services/ directory.
WARNING: Be careful when you cut-n-paste the php code snippets. Amfphp will throw an error if the php files begin with whitespace or carriage return. Make sure nothing preceeds your opening php tag.
The Datasource.php class is a helper class used for communicating with your database.
Open your text editor and create a new file. Cut-n-paste the php code below into your new file. Save the file as Datasource.php under your [PROJECT_HOME]/src/php/amfphp/services/org/amfphp/tutorials/ directory.
<?php
class Datasource
{
var $dbLink;
function Datasource($dbHost, $dbName, $dbuser, $dbpasswd)
{
$this->dbLink= mysql_connect ($dbHost,
$dbuser, $dbpasswd);
mysql_select_db ($dbName,$this->dbLink);
}
function _execute($sql)
{< br > $result=
mysql_query($sql,$this->dbLink);<br> $this->_checkErrors($sql);
return $result;
}
function _executeBlind($sql)
{
$result =
mysql_query($sql,$this->dbLink);
return $result;
}
function _nextRow ($result)
{
$row = mysql_fetch_array($result);
return $row;
}
function _checkErrors($sql)
{
$err=mysql_error();
$errno=mysql_errno();
if($errno)
{
$message = "The following
SQL command ".$sql." caused Database error: ".$err.".";
print "Unrecowerable
error has occurred. All data will be logged.";
print "Please contact
System Administrator for help! \n";
print "
\n";
exit;
}
else
{
return;
}
}
}
?>
The Employee class is your value object. The $_explicitType variable informs Amfphp that this class maps to an actionscript class in your Flex application.
Open your text editor and create a new file. Cut-n-paste the php code below into your new file. Save the file as Employee.php under your [PROJECT_HOME]/src/php/amfphp/services/org/amfphp/tutorials/ directory.
<?php
class Employee
{
var $empId;
var $firstName;
var $lastName;
var $phone;
var $email;
var $title;
// explicit actionscript package
var $_explicitType = "org.amfphp.tutorials.Employee";
}
?>
The EmployeeDao.php class provides methods for preforming the basic CRUD operation. The save and remove methods both expected associative array's as parameters and return Employee objects.
Open your text editor and create a new file. Cut-n-paste the php code below into your new file. Save the file as EmployeeDao.php under your [PROJECT_HOME]/src/php/amfphp/services/org/amfphp/tutorials/ directory. Depending on how you setup your database you might need to modify the EmployeeDao contructor to reflect you database settings.
<?php
require_once('./Employee.php');
require_once('./Datasource.php');
class EmployeeDao
{
var $conn;
function EmployeeDao()
{
$this->conn = new
Datasource("localhost", "test", "", "");
}
function load($empId)
{
if (!$empId)
{
return
false;
}
$sql = "SELECT * FROM
Employee WHERE (empId = ". $empId .") ";
return$this->_singleQuery($sql);
}
function loadAll()
{
$sql = "SELECT * FROM
Employee ORDER BY empId ASC ";
$searchResults =
$this->_listQuery($sql);
return $searchResults;
}
function _create($valueObject)
{
$sql = "INSERT INTO
Employee ( firstName, lastName, phone, ";
$sql = $sql."email,
title) VALUES ('".$valueObject[firstName]."', ";
$sql =
$sql."'".$valueObject[lastName]."', ";
$sql =
$sql."'".$valueObject[phone]."', ";
$sql =
$sql."'".$valueObject[email]."', ";
$sql =
$sql."'".$valueObject[title]."') ";
$result =
$this->_databaseUpdate($sql);
$sql = "SELECT
last_insert_id()";< br >
$result=$this->conn->_execute($sql);
if
($row=$this->conn->_nextRow($result))
{
return
$row[0];
}
else
{
return
false;
}
}
function save($valueObject)
{
if(
array_key_exists("empId",$valueObject) )
{
if
($valueObject[empId] == 0)
{
$insertId
= $this->_create($valueObject);
if($insertId)
{
$temp
= new Employee();< br >
$temp->empId=
$insertId;<br> $temp->firstName
= $valueObject[firstName];
$temp->lastName
= $valueObject[lastName];
$temp->phone
= $valueObject[phone];
$temp->email
= $valueObject[email];
$temp->title
= $valueObject[title];
return
$temp;
}
}
else
{
if($this->_update($valueObject))
{
$temp
= new Employee();
$temp->empId
= $valueObject[empId];
$temp->firstName
= $valueObject[firstName];
$temp->lastName
= $valueObject[lastName];
$temp->phone
= $valueObject[phone];
$temp->email
= $valueObject[email];
$temp->title
= $valueObject[title];
return
$temp;
}
}
}
return false;
}
function _update($valueObject)
{
$sql = "UPDATE Employee
SET firstName = '".$valueObject[firstName]."', ";
$sql = $sql."lastName =
'".$valueObject[lastName]."', ";
$sql = $sql."phone =
'".$valueObject[phone]."', ";
$sql = $sql."email =
'".$valueObject[email]."', ";
$sql = $sql."title =
'".$valueObject[title]."'";
$sql = $sql." WHERE
(empId = ".$valueObject[empId].") ";
$result =
$this->_databaseUpdate($sql);
if ($result != 1)
{
return
false;
}
return true;
}
function remove($valueObject)
{
if
(!array_key_exists("empId",$valueObject) )
{
return
false;
}
$sql = "DELETE FROM
Employee WHERE (empId = ".$valueObject[empId].") ";
$result =
$this->_databaseUpdate($sql);
if ($result != 1)
{
return
false;
}
$temp = new Employee();<
br > $temp->empId=
$valueObject[empId];<
br> $temp->firstName=
$valueObject[firstName];<
br> $temp->lastName=
$valueObject[lastName];<br> $temp->phone
= $valueObject[phone]; < br >
$temp->email=
$valueObject[email];<br> $temp->title
= $valueObject[title];
return $temp;
}
function removeAll()
{
$sql = "DELETE FROM
Employee";
$result =
$this->_databaseUpdate($sql);
return true;
}
function _databaseUpdate($sql)
{< br >
$result=$this->conn->_execute($sql);
return $result;
}
function _singleQuery($sql)
{
$valueObject = new
Employee();< br >
$result=$this->conn->_execute($sql);
if
($row=$this->conn->_nextRow($result))< br>
{<br> $valueObject->empId
= $row[0];
<br> $valueObject->firstName
= $row[1];
<br> $valueObject->lastName
= $row[2];
<br> $valueObject->phone
= $row[3];
<br> $valueObject->email
= $row[4];
<br> $valueObject->title
= $row[5];
}
else
{
return
false;
}
return $valueObject;
}
function _listQuery($sql)
{
$searchResults =
array();< br >
$result=$this->conn->_execute($sql);
while
($row=$this->conn->_nextRow($result))
{
$temp =
new
Employee();<br> $temp->empId
= $row[0];
<br> $temp->firstName
= $row[1];
<br> $temp->lastName
= $row[2];
<br> $temp->phone
= $row[3];
<br> $temp->email
= $row[4];
<br> $temp->title
= $row[5];
array_push($searchResults,
$temp);
}
return $searchResults;
}
}
?>
Open your text editor and create a new file. Cut-n-paste the xml code below into your new file. Save the file as services-config.xml under your [PROJECT_HOME]/config/flex/ directory.
<?xml version="1.0" encoding="UTF-8"?>
<services-config>
<services>
<service-include
file-path="remoting-config.xml" />
</services>
<channels>
<channel-definition id="my-amfphp"
class="mx.messaging.channels.AMFChannel">
<endpoint
uri="http://{server.name}:{server.port}/{context.root}/amfphp/gateway.php"
class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>
</channels>
</services-config>
Open your text editor and create a new file. Cut-n-paste the xml code below into your new file. Save the file as remoting-config.xml under your [PROJECT_HOME]/config/flex/ directory.
<?xml version="1.0" encoding="utf-8" ?>
<service id="amfphp-flashremoting-service"
class="flex.messaging.services.RemotingService"
messageTypes="flex.messaging.messages.RemotingMessage">
<default-channels>
<channel ref="my-amfphp"/>
</default-channels>
<destination id="empService">
<properties>
<source>*</source>
</properties>
</destination>
</service>
Our actionscript classes will be using the same package style naming as our php classes. Create the following directory structure under your [PROJECT_HOME]/src/flex/ directory.
Open your text editor and create a new file. Cut-n-paste the actionscript code below into your new file. Save the file as Employee.as under your [PROJECT_HOME]/src/flex/org/amfphp/tutorials/ directory.
package org.amfphp.tutorials
{
[RemoteClass(alias="org.amfphp.tutorials.Employee")]
[Bindable]
public class Employee
{
public var empId:int;
public var firstName:String;
public var lastName:String;
public var phone:String;
public var email:String;
public var title:String;
}
}
Open your text editor and create a new file. Cut-n-paste the mxml code below into your new file. Save the file as Main.mxml under your [PROJECT_HOME]/src/flex directory.
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
backgroundColor="#FFFFFF">
<mx:Script>
<![CDATA[
import
mx.utils.ArrayUtil;
import
org.amfphp.tutorials.Employee;
import
mx.collections.ArrayCollection;
import
mx.rpc.events.ResultEvent;
import mx.controls.Alert;
import
mx.rpc.events.FaultEvent;
[Bindable]
private var
dp:ArrayCollection;
[Bindable]
private var emp:Employee
= new Employee();
private var index:Number;
private var token:Object;
private function
faultHandler(fault:FaultEvent):void
{
Alert.show(fault.fault.faultString
+ "\n" + fault.fault.faultDetail, fault.fault.faultCode.toString());
}
private function
resultHandler(event:ResultEvent):void
{
dp =
new ArrayCollection( ArrayUtil.toArray(event.result) );
}
private function
changeHandler(event:Event):void
{
emp =
Employee(DataGrid(event.target).selectedItem);
}
private function
saveHandler(event:ResultEvent):void
{
index =
Number(event.token.index);
if(
index > -1 )
{
dp.setItemAt(event.result,index);
}
else
{
dp.addItem(event.result);
}
}
private function
removeHandler(event:ResultEvent):void
{
index =
Number(event.token.index);
if(
index > -1 )
{
dp.removeItemAt(index);
}
}
private function
remove():void
{
token =
empRO.remove.send(emp);
token.index
= dp.getItemIndex(emp);
emp =
new Employee();
}
private function
cancel():void
{
emp =
new Employee();
}
private function
save():void
{
var
newEmp:Employee = new Employee();
newEmp.empId
= emp.empId;
newEmp.firstName
= employee_first_name.text;
newEmp.lastName
= employee_last_name.text;
newEmp.phone
= employee_phone.text;
newEmp.email
= employee_email.text;
newEmp.title
= employee_title.text;
token =
empRO.save.send(newEmp);
token.index
= dp.getItemIndex(emp);
emp =
new Employee();
}
]]>
</mx:Script>
<mx:RemoteObject id="empRO" destination="empService"
source="org.amfphp.tutorials.EmployeeDao" fault="faultHandler(event)"
showBusyCursor="true">
<mx:method name="loadAll"
result="resultHandler(event)"/>
<mx:method name="save"
result="saveHandler(event)"/>
<mx:method name="remove"
result="removeHandler(event)" />
</mx:RemoteObject>
<mx:DataGrid width="345" id="employee_list"
dataProvider="{dp}" change="changeHandler(event)">
<mx:columns>
<mx:DataGridColumn
headerText="Last name" dataField="lastName"/>
<mx:DataGridColumn
headerText="First name" dataField="firstName"/>
<mx:DataGridColumn
headerText="Telephone" dataField="phone"/>
<mx:DataGridColumn
headerText="Email" dataField="email"/>
<mx:DataGridColumn
headerText="Title" dataField="title"/>
</mx:columns>
</mx:DataGrid>
<mx:Button label="Get Employee List"
click="empRO.loadAll.send();"/>
<mx:Form width="345" height="200">
<mx:FormHeading label="Selected
Employee" />
<mx:FormItem label="First Name">
<mx:TextInput
id="employee_first_name" text="{emp.firstName}" />
</mx:FormItem>
<mx:FormItem label="Last Name">
<mx:TextInput
id="employee_last_name" text="{emp.lastName}"/>
</mx:FormItem>
<mx:FormItem label="Telephone">
<mx:TextInput
id="employee_phone" text="{emp.phone}" />
</mx:FormItem>
<mx:FormItem label="Email">
<mx:TextInput
id="employee_email" text="{emp.email}"/>
</mx:FormItem>
<mx:FormItem label="Title">
<mx:TextInput
id="employee_title" text="{emp.title}"/>
</mx:FormItem>
</mx:Form>
<mx:HBox>
<mx:Button id="btnSave" click="save();"
label="Save" enabled="{dp != null}" />
<mx:Button id="btnDelete"
click="remove();" label="Delete" enabled="{employee_list.selectedIndex !=
-1}"/>
<mx:Button id="btnCancel"
click="cancel();" label="Cancel"/>
</mx:HBox>
</mx:Application>
Open your text editor and create a new file. Add the line below to the very beginning of the file. This line will identify this file as an XML file. It is very important that no text or whitespace preceed this line. Now save this file as build.xml under your [PROJECT_HOME] directory.
<?xml version="1.0" encoding="utf-8"?>
Next, we define our project using Ant's project tag. Using the tag's name and basedir attributes we specify a project name and base directory. Setting the basedir attribute to a period indcates that it will use the current directory. The remaining Ant tags we will be defining should be placed inside this project tag.
<project name="My Flex App" basedir=".">
</project>
To use the Flex Ant Task library we must define a task definition. We do this by adding the following task definition tag immediately after are opening project tag.
<taskdef resource="flexTasks.tasks"/>
After the task definition tag we define some Ant properties that we will be using in our build file. The first and most important property is the FLEX_HOME property. The FLEX_HOME property is required by the Flex Ant Task to locate the Flex compilers. Create a FLEX_HOME property and set its location attribute to the [FLEX_SDK] directory (example: C:\Flex). Create a app.name property and set its value attribute to any name you like. We will refer to this name as [APP_NAME]. The app.name property is used as the zip file name when you distribute your project and it is also passed to the mxmlc compiler as the context-root name. The context-root is used when referencing your Flex application on the URL so it is important that your app.name property not contain any special characters or spaces. We will refer to your server as [SERVER_NAME].
http://[SERVER_NAME]/[APP_NAME]
The remaining properties defined are used to provide short-cuts to directories in the [PROJECT_HOME] directory.
<propertyname="FLEX_HOME" location="[FLEX_SDK]"/>
<property name="app.name" value="[APP_NAME]"/><
br><propertyname="build.home" location="${basedir}/build"/>
<property name="src.home" location="${basedir}/src"/>
<property name="web.home" location="${basedir}/web"/>
<property name="lib.home" location="${basedir}/lib"/><
br><propertyname="config.home" location="${basedir}/config"/>
<property name="dist.home" location="${basedir}/dist"/>
<property name="docs.home" location="${basedir}/docs"/>
Now we define our first target. The clean target will be used to clean-up between builds and distributions.
<target name="clean">
<delete includeemptydirs="true">
<fileset dir="${build.home}"
includes="**/*"/>
<fileset dir="${dist.home}"
includes="**/*"/>
</delete>
</target>
The prepare target will prepare the build directory for distribution. It begins by creating the build/${app.name} directory if it does not already exist. It then copies our php source and web directories over to the build/${app.name} directory.
<target name="prepare">
<mkdir dir="${build.home}/${app.name}"/>
<copy todir="${build.home}/${app.name}">
<fileset dir="${src.home}/php"/>
<fileset dir="${web.home}"/>
</copy>
</target>
The compile target will compile our Main.mxml file using the Flex Ant Task mxmlc tag. The mxmlc tags contains several attributes and subtags that control how Flex will complie our applicaton.
<target name="compile" depends="prepare">
<mxmlc
file="${src.home}/flex/Main.mxml"
output="${build.home}/${app.name}/Main.swf"
actionscript-file-encoding="UTF-8"
context-root="${app.name}"
services="${config.home}/flex/services-config.xml">
<!-- Get default compiler options.
-->
<load-config
filename="${FLEX_HOME}/frameworks/flex-config.xml"/>
<!-- List of path elements that form the
roots of ActionScript class hierarchies. -->
<source-path
path-element="${FLEX_HOME}/frameworks"/>
<source-path
path-element="${src.home}/flex"/>
<!-- List of SWC files or directories
that contain SWC files. -->
<compiler.library-path
dir="${FLEX_HOME}/frameworks" append="true">
<include
name="libs" />
<include
name="../bundles/{locale}" />
<include
name="${lib.home}/flex/" />
</compiler.library-path>
<!-- Set size of output SWF file. -->
<default-size width="500" height="600"
/>
</mxmlc>
</target>
The distribute target will create a zip file of our project for easy distribution.
<target name="dist" depends="compile">
<zip destfile="${dist.home}/${app.name}.zip"
basedir="${build.home}"
update="true"/>
</target>
The deploy target is optional. I provided it to make project deployment even easier for those using remote servers. If you want to use the FTP task ant provides you must have commons-net-1.4.1.jar and jakarta-oro-2.0.8.jar in the [ANT_HOME]/lib directory. Replace the server attribute with your server. The app.name property in the remotedir attribute must exist prior to running the deploy task. Do not remove the app.name property from the remotedir attribute because your Flex application was compiled using app.name as the context-root.
<target name="deploy" depends="compile">
<ftp server="[SERVER_NAME]"
remotedir="/home/username/www/${app.name}"
userid="username"
password="password"
depends="yes">
<fileset
dir="${build.home}/${app.name}"/>
</ftp>
</target>
Run the following commands from your [PROJECT_HOME] directory. Windows users can use the command-prompt. Linux and Mac users can use the terminal.
To compile your project run the compile task. When the compile task has finished executing you will have a directory in your [PROJECT_HOME]/build directory with the name you specified in the app.name property. Copy or ftp the app.name directory to your web server's document root directory.
ant compile
To deploy your project run the deploy task. This task will ftp the contents of the [PROJECT_HOME]/build directory to your web server's document root directory.
ant deploy
To distribute your project run the dist task. This task will zip the contents of the [PROJECT_HOME]/build directory. The zip file name will be the one you specified in the app.name property.
ant dist
Before running our Flex application we need to test our php services to ensure that are functioning correctly. We can test our php services using the Amfphp services browser. Substitute <context-root> with the value you used for the app.name propery.
http://[SERVER_NAME]/[APP_NAME]/amfphp/browser/
http://[SERVER_NAME]/[APP_NAME]/Main.swf/