As mentioned on the previous page, there are (at least) three ways to integrate Flash with an online database. In that example, we looked at using instances of the LoadVars class to do Flash-server communications. On this page, we'll use the Service, PendingCall and RelayResponder classes to communicate with database via AMFPHP, an open source remoting package for Apache-based servers which uses binary data transfer (more efficient than ascii transfer used by LoadVars and XML-based communications). Using remoting also allows preformatted objects like recordsets to be passed to Flash, which can be directly applied as dataproviders to Flash components.
The instuctions at the AMFPHP WiKi very nicely spell out what you need to have and do to get AMFPHP up and running on a Windows or Linux based web server. In the code below, we'll assume that amfphp has been installed in myamfphp directory at the root level.
Using exactly the same table setup and Flash layout as in the previous example, the only things we need to change are:
To use remoting in your Flash movie, you'll first need to import the relevant classes. These are the import statements for this movie (including Delegate, not related to remoting):
import mx.remoting.*; import mx.rpc.*; import mx.remoting.debug.NetDebug; import mx.utils.Delegate;
To communicate with your serverside class via remoting, you'll need to specify the path to gateway.php and create a new Service instance (and initialize the debugger if you plan to use that). If amfphp is installed in directory myamfphp, and the class is called Highscores, then:
var gatewayUrl:String = "http://yourdomain.com/myamfphp/gateway.php" NetDebug.initialize(); var _service:Service = new Service(gatewayUrl, null, 'Highscores', null , null);
Initialization of objects on stage, header event handlers, etc, remain the same as in the previous example. To send the call that retrieves score data from the database an instance of PendingCall will be created. The following code creates a new PendingCall instance to call the getScores method of Highscores (our PHP class, saved in Highscores.php in the services directory under myamfphp), and then call onGetScores or onError when finished:
msg_ta.text = "Getting high scores from database..."; var pc:PendingCall = _service.getScores(); pc.responder = new RelayResponder(this, "onGetScores", "onError");
onError will be called if the Flash Remoting gateway encounters an error or if a service function throws an exception. Database errors such as specifying the database or table name incorrectly will result in onGetScores (not onError) being called with an errorcode (eg, re.result = false).
When the getScores method of HighScores is called, a recordset containing the query results is passed back to Flash. This is actually an instance of the RecordSet class, as described in the Flash help files under Flash Remoting Actionscript Dictionary, All Classes, RecordSet. This RecordSet instance can be assigned directly to the dataProvider property of the datagrid. Note that this means the methods applicable to the dataProvider are now those of a RecordSet instance. In our case, that means that the sortItemsBy signature we must apply if we want to use a custom sort is the one for the RecordSet class. The new custom sort function now looks like:
// create an object to listen for clicks on datagrid headers to do correct sort
var headerListener:Object = {};
headerListener.headerRelease = function(event:Object) {
switch (event.columnIndex) {
case 0:
if (scores_dg.getColumnAt(0).sortedUp) {
// dataProvider is a RecordSet (not Array) -- must use RecordSet.sortItemsBy signature
scores_dg.dataProvider.sortItemsBy(scores_dg.columnNames[0], null, Array.CASEINSENSITIVE | Array.DESCENDING);
} else {
scores_dg.dataProvider.sortItemsBy(scores_dg.columnNames[0], null, Array.CASEINSENSITIVE);
}
scores_dg.getColumnAt(0).sortedUp = !scores_dg.getColumnAt(0).sortedUp;
break;
case 1:
if (scores_dg.getColumnAt(1).sortedUp) {
scores_dg.dataProvider.sortItemsBy(scores_dg.columnNames[1], null, Array.NUMERIC | Array.DESCENDING);
} else {
scores_dg.dataProvider.sortItemsBy(scores_dg.columnNames[1], null, Array.NUMERIC);
}
scores_dg.getColumnAt(1).sortedUp = !scores_dg.getColumnAt(1).sortedUp;
break;
}
}
And following is the code in onGetScores itself, which either displays an error message if one occurred or fills the datagrid with returned data. Notice that data is returned in the result property of whatever parameter you specify in the handler function (re in our case). The type of data to be returned is specified in the PHP file (which we'll look at in a minute), and in this case is either boolean, numeric, or a recordset, which can be used as is as the dataProvider for a v2 component:
// function to format scores datagrid and set provider to recordset returned from getScores
// will be called even if wrong table name in getScores query or bad database name
function onGetScores(re:ResultEvent) {
// in case you want to examine what was returned, uncomment this:
// trace('onGetScores: '+re.result+' '+typeof re.result+' '+re.result.length);
// returns re.result = false (typeof boolean) if bad db name
if (re.result != false) {
// only display Nickname, Score, and Date Posted (not record id)
scores_dg.columnNames = ["nickname", "score", "dateposted"];
// ... formatting, as on previous page
scores_dg.getColumnAt(2).headerText = "Date Posted";
// set dataProvider for datagrid (DIFFERENT FROM LOADVARS)
scores_dg.dataProvider = re.result;
// execute headerRelease function for correct sort when user clicks a header
scores_dg.addEventListener("headerRelease", headerListener);
msg_ta.text = "Enter data and click Add to add a score. Click a row and Delete Selected to delete a score. First four entries may not be deleted.";
} else {
msg_ta.text = "Problem connecting with the database";
}
}
The code for inserting a record into the database looks quite similar on the Flash side to that in our previous example. The code order is backwards from the order in which it gets executed: the Add button calls insertRecord, which makes the remoting call, which calls onInsertScore (or onError) when done:
function onInsertScore(re:ResultEvent) {
// again, uncomment if you want to see what gets returned:
//trace(re.result+' '+typeof re.result);
// if a number was returned, it is the id of the record just added
if (!isNaN(re.result)) {
// update the dataProvider so datagrid updates
scores_dg.dataProvider.addItem(
{id:Number(re.result),
nickname:nickname_ti.text,
score:Number(score_ti.text),
dateposted:date_ti.text
});
msg_ta.text = "Score was added to the database";
} else {
msg_ta.text = "Score could not be added";
}
// reset fields
nickname_ti.text = '';
score_ti.text = 100;
date_ti.text = today.getFullYear() + '-' +
zerofill(today.getMonth()+1) + '-' +
zerofill(today.getDate());
}
function insertRecord() {
var newScore:Object = {};
// check for no name or bad score
if (nickname_ti.text == '') {
msg_ta.text = "Please enter your name";
} else if (isNaN(score_ti.text) ||
Number(score_ti.text) < 0 ||
Number(score_ti.text) > 65000) {
msg_ta.text = "Score must be a number between 0 and 65000";
} else {
// set up an object to pass to insertScore
newScore.nickname = nickname_ti.text;
newScore.score = score_ti.text;
newScore.dateposted = date_ti.text;
// redefine the PendingCall instance (previously used for select) to do the insert
pc = _service.insertScore(newScore);
pc.responder = new RelayResponder(this, "onInsertScore", "onError");
}
}
add_btn.addEventListener("click", Delegate.create(this, insertRecord));
The delete code is similar. Delete button calls deleteRecord, which carries out remoting function to delete record and then call onDeleteRecord (or onError) when done:
function onDeleteScore(re:ResultEvent) {
//trace('result='+re.result);
if (re.result) {
scores_dg.removeItemAt(deleteIndex);
msg_ta.text = "Record was deleted";
} else {
msg_ta.text = "Record could not be deleted";
}
}
function deleteRecord() {
// remember which element of the array is to be deleted
deleteIndex = scores_dg.selectedIndex;
pc = _service.deleteScore(scores_dg.selectedItem.id);
pc.responder = new RelayResponder(this, "onDeleteScore", "onError");
}
delete_btn.addEventListener("click", Delegate.create(this, deleteRecord));
The PHP side of things is much simpler than the multiple files used in the previous example. PHP class Highscores contains, like all remoting classes, property declarations, a constructor, containing the method table describing all class methods and the database connection, and each individual method that will be needed by the application. In simplest form:
<?
class Highscores {
var $dbhost = ''; // your information here
var $dbname = '';
var $dbuser = ''
var $dbpass = '';
function Highscores() {
$this->methodTable = array(
"getScores" => array(
"description" => "Returns rs of all scores from highscores table",
"access" => "remote",
"returntype" => "recordSet"
),
"insertScore" => array(
"description" => "Insert score into highscores table, return id",
"access" => "remote"
),
"deleteScore" => array(
"description" => "Deletes score for specified id from highscores",
"access" => "remote"
)
);
// Initialize db connection
$this->conn = mysql_pconnect($this->dbhost, $this->dbuser, $this->dbpass);
mysql_select_db ($this->dbname);
}
function getScores() {
return mysql_query("SELECT * FROM highscores");
}
function insertScore($scoreData) {
mysql_query("INSERT INTO highscores VALUES (NULL,
'".addslashes($scoreData['nickname'])."',
'".$scoreData['dateposted']."',
'".$scoreData['score']."')");
return mysql_insert_id();
}
function deleteScore($id) {
return mysql_query("DELETE FROM highscores WHERE id=".$id);
}
}
?>
The code above with the layout and tables on the previous page will produce a Flash sample that functions identically (in outward appearance) to that on the previous page (except for the wording of the error messages and checking for deletion of the first 4 ids in the table).
One other thing to note is that if the PHP class file is not correct, the error returned via remoting will not help you determine the cause of the error. It's best to write a PHP script (or other non-remoting test) to check your class before using it. This is a simple PHP script I used to make sure the getScores method of Highscores was at least finding the correct number of score records in the database:
<?
require_once("myamfphp/services/Highscores.php");
echo "getting scores...<br>";
$h = new HighScores;
$q = $h->getScores();
echo mysql_num_rows($q);
?>
Similar scripts could be written to test the insert and delete methods.
last update: 17 Jan 2006
Discussed on this page:
using remoting for binary data communication between flash and web server, amfphp, remoting components, php class, methodtable, recordset sort
Files:
highscores_remoting.fla
HighScores.php
testHighScores.php
(free download)
A list of all files currently available at the site may be viewed here.