- AMFPHP Recipes
- Web Service Recipes
Air Recipes- Cairngorm Recipes
Introduction
Here is a little thing that I made up for people using Flex/Flash and amfphp a little cheat sheet to use as a reference.
This has been completely re-done since it has been so popular. I have commented all of the code very well so that does most of the explaining of what code does what. I also added a few more calls such as sending email, sending images, sending files, searching and pagination.
For use without any framework and without a services-config.xml file in the compiler.
These examples are snippets from my Flash Remoting presentation and Snippr Air application. Source files will be provided at the end, also there will be a PDF version of this for easy printing.
So Please print and share with everyone who would find any use in this.
- Recipe 1.1. Installing AMFPHP
- Recipe 1.2. Connecting to MySQL
- Recipe 1.3. Using Classes with AMFPHP
- Recipe 1.4. Class Mapping
- Recipe 1.5. Building CRUD for AMFPHP
- Recipe 1.6. Making Calls
- Recipe 1.7. Creating a Service Proxy
- Recipe 1.8. Sending Emails with AMFPHP
- Recipe 1.9. Sending Images with AMFPHP
- Recipe 1.10. PHP to ActionScript Datatypes
Recipe 1.1 – AMFPHP Installation
Problem: You want to start using amfphp, but you do not know where to start.
Solution: Download amfphp1.9 from http://amfphp.org, unzip the folder, then drop it into your web root.
Server folder structure:
Recipe 1.2 – Connecting to MySQL
Problem: I have amfphp installed, now how they heck to I connect to my database?
Solution: Create a simple class file for connecting to MySQL, and include it in any of our classes that need database interaction.
Code: For connecting to a database make your connection file in a separate file.
Separate File
Connection.php:
class Connection
{
private $link;
public function __construct( $host, $user, $pass, $database )
{
$this->link = new mysqli( $host, $user, $pass, $database );
return $this->link;
}
}
Inside the separate file you return a variable that you will use as a link to connect to the database, very handy for connecting to one database in multiple files.
Recipe 1.3 – Using AMFPHP with PHP Classes
Problem: You want to connect Flex to PHP code via AMFPHP, you need to write your PHP code as a PHP class. The remote methods that your ActionScript code calls are the methods of your custom classes.
Solution: Creating a class both in PHP and ActionScript.
A PHP class uses the following syntax:
class MyClass
{
var local1 = 1;
var local2 = 2;
public function MyClass()
{
//do something
}
public function someMethod( $arg )
{
//do something else
}
}
An ActionScript class uses this following syntax:
package
{
class MyClass
{
public var someVariable:Number;
public var anotherVariable:Number;
public function MyClass()
{
//do something
}
public function someFunction( arg:Object )
{
//do something else
}
}
}
Recipe 1.4 – Class Mapping
Problem: You want to specify the data that is being returned from AMFPHP, but do not know how to go about doing so.
Solution: Class mapping allows you to use a remote PHP object to be mapped directly to your ActionScript object, without having to build generic code to iterate through all of the results coming back.
This is very useful when coding in Flex Builder, for one you have code hinting that will help you if you don’t remember the exact name of some of the objects and such, and for two it enforces the type that you have defined in your value object in ActionScript is what you are using it for. If that makes any sense.
If you specify that snippet_id inside your value object is a ByteArray.
public var snippet_id:ByteArray;Then when you go to create a new snippet to send to AMFPHP you cannot set snippet_id to a String or Number.
var snippet:SnippetVO = new SnippetVO(); snippet.snippet_id = txt_name.text;
Code:
SnippetVO.php
class SnippetVO
{
private $snippet_id;
private $snippet_title;
private $snippet_code;
private $snippet_type;
private $snippet_created;
private $snippet_user;
public function __construct( $obj )
{
$this->snippet_id = $obj['snippet_id'];
$this->snippet_title = $obj['snippet_title'];
$this->snippet_code = $obj['snippet_code'];
$this->snippet_type = $obj['snippet_type'];
$this->snippet_created = $obj['snippet_created'];
$this->snippet_user = $obj['snippet_user'];
}
}
On this value object we have a mapObject function that maps our specified variables to the names of the table columns, so we will know exactly what is coming back and we can call those fields by the there variable.
Our explicitType is the location of our client side value object. Because when Flex sends the vo its "id" is on the client, so we are letting php know that this explicitType is what we are excepting.
SnippetVO.as
package com.jonniespratley.snippr.vo
{
[RemoteClass(alias="vo.SnippetVO")]
[Bindable]
public class SnippetVO
{
public var snippet_id:int;
public var snippet_title:String;
public var snippet_code:String;
public var snippet_type:String;
public var snippet_created:String;
public var snippet_user:String;
public function SnippetVO()
{
}
}
}
For the ActionScript value object we have the same variables that we have inside of our php object, but in our constructor we have a function that loops through every item so later on when we pass this array to our array collection in our model, we will use a special little function for parsing up that data.
Our remote alias is set the the location of the server side value object.
Recipe 1.5 – Building C.R.U.D for Amfphp
Problem: You want to create, read, update and delete records in your MySQL database from AMFPHP.
Solution: Creating a CRUD service class for AMFPHP.
Code: All of the code is commented, and this should help you solve your problem and have full CRUD of your database.
SnipprService.php
class SnipprService
{
//Specify our table name
private $table = "snippets";
public function __construct()
{
mysql_connect ( "localhost", "spratley_guest", "guest" );
mysql_select_db ( "spratley_snippr" );
}
private function mapRecordSet( $recordset )
{
require_once ( "../vo/SnippetVO.php" );
$list = array ();
while ( $data = mysql_fetch_array ( $recordset ) )
{
$vo = new SnippetVO ( $data );
array_push ( $list, $vo );
}
return $vo;
}
public function getSnippets()
{
//We must specify our vo, because we need to map correctly
require_once ( "../vo/SnippetVO.php" );
$sql = mysql_query ( "SELECT * FROM " . $this->table . "" );
$result = array ();
while ( $snip = mysql_fetch_array ( $sql ) )
{
//Create a new snippet vo
$snippet = new SnippetVO ( $snip );
//Result is a snippet
$result [] = $snippet;
}
//return out the result
return $result;
}
//This is used for returning the created or updated snippet for flex
public function getOne( $id )
{
$rs = mysql_query ( "SELECT * FROM " . $this->table . " WHERE snippet_id = " . $id );
//Map the recordset to our vo
$list = $this->mapRecordSet ( $rs );
//Return our vo
return $list;
}
public function saveSnippet( $snippet )
{
require_once ( "../vo/SnippetVO.php" );
//Check to see if the snippet has an id of
if ( $snippet[snippet_id] == '' )
{
$query = "INSERT INTO " . $this->table . "
( snippet_title,
snippet_code,
snippet_type,
snippet_created,
snippet_user )
VALUES (
'" . mysql_real_escape_string ( $snippet [ snippet_title ] ) . "',
'" . mysql_real_escape_string ( $snippet [ snippet_code ] ) . "',
'" . mysql_real_escape_string ( $snippet [ snippet_type ] ) . "',
'" . mysql_real_escape_string ( $snippet [ snippet_created ] ) . "',
'" . mysql_real_escape_string ( $snippet [ snippet_user ] ) . "')";
if ( ! mysql_query ( $query ) )
{
return false;
}
return $this->getOne ( mysql_insert_id () );
} else {
$id = $snippet [ snippet_id ];
$query = "UPDATE " . $this->table . " SET
snippet_title = '" . mysql_real_escape_string ( $snippet [ snippet_title ] ) . "'
snippet_code = '" . mysql_real_escape_string ( $snippet [ snippet_code ] ) . "',
snippet_type = '" . mysql_real_escape_string ( $snippet [ snippet_type ] ) . "',
snippet_created = '" . mysql_real_escape_string ( $snippet [ snippet_created ] ) . "',
snippet_user = '" . mysql_real_escape_string ( $snippet [ snippet_user ] ) . "'
WHERE snippet_id =" . $id;
if ( ! mysql_query ( $query ) )
{
return false;
}
//Return the created snippet
return $this->getOne ( $id );
}
}
public function removeSnippet( $id )
{
$sql = mysql_query ( "DELETE FROM " . $this->table . " WHERE snippet_id = " . $id );
if ( ! $sql )
{
trigger_error("Unable to delete Snippets", E_USER_ERROR);
return "There was an error removing this snippet";
} else {
return $id;
}
}
Recipe 1.6 – Making Calls
Problem:You want to actually start using AMFPHP since you have the database set up, a full CRUD service and all necessary value objects.
Solution: Before we can make any calls to AMFPHP we have to let Flex know where our gateway.php is, connecting to AMFPHP is probably one of the easiest things to do. It only takes 3 steps to connect to our server side remoting.
1. A NetConnection variable
private var service:NetConnection = new NetConnection();2. A String variable
private var gateway:String = "http://localhost/amfphp/gateway.php";3. Now connect
service.connect( gateway );
Code:
public function getSomeData():void
{
service.call ( "Path.ServiceName.MethodName", new Responder( someResultHandler, someFaultHandler ), Arguments ) );
}
And we are now connected to AMFPHP, no service-config.xml, no RemoteObject tags, just as above and you will be connected.
To make actual calls to a method that you have created you use syntax like the following:
Recipe 1.7 – Creating a Service Proxy
Problem: You have all service methods on your server working as expected, but you have no access to them from Flex.
Solution: Create a Service Proxy in ActionScript containing all necessary functions for connecting, calling methods and handling the result for your views
Code:
SnipprService.as
package com.jonniespratley.snippr.services
{
import com.jonniespratley.snippr.model.ModelLocator;
import com.jonniespratley.snippr.vo.EmailVO;
import com.jonniespratley.snippr.vo.SnippetVO;
import flash.net.NetConnection;
import flash.net.Responder;
import flash.utils.ByteArray;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.rpc.events.ResultEvent;
/**
* This file is for use without! using the services-config.xml file
* @author Jonnie Spratley
* @website http://jonniespratley.com
*
*/
public class SnipprService
{
/** NetConnection variable for creating our amfphp connection */
private static var _service:NetConnection;
/** Location of our gateway for amfphp */
private var gateway:String = "http://localhost/snippr/amfphp/gateway.php";
/** Our model so we can update it when we receive our data */
private var model:ModelLocator = ModelLocator.getInstance();
/**
* Here we are creating a new connection to our amfphp service, when this is instantiated,
* it connects to our service.
*
*/
public function SnipprService()
{
_service = new NetConnection();
_service.connect( gateway );
}
/* **********************************************************************
* All Service Calls to AMFPHP (updated)
*
* This is where all of our service calls are taken, when our
* outside componets calls these functions all required arguemtns
* must be passed to properly send/update/delete data.
*
* If arguments are not present Flex wont compile. In all of our
* calls we attach assigned result handlers for the specific calls
* that we are making. They all use the same fault handler.
************************************************************************/
/**
* Here we are calling the getSnippets on our server (amfphp) and setting the result and fault handlers
*
*/
public function getSnippets():void
{
_service.call( "snippr.SnipprService.getSnippets", new Responder( snippetResultHandler, snipprFaultHandler ) );
trace( "Gettings Snippets" );
}
/**
* We take one argument here, and that is a snippet, because our server (amfphp) is expecting a snippetVO
*
* @param snippet snippetVO object
*
*/
public function saveSnippet( snippet:SnippetVO ):void
{
_service.call( "snippr.SnipprService.saveSnippet", new Responder( snippetSavedHandler, snipprFaultHandler ), snippet );
trace( "Saving Snippet" );
}
/**
* We take one argument here, and that is the id of the snippet we are wanting to remove
*
* @param snippet_id the id to be removed
*
*/
public function removeSnippet( snippet_id:uint ):void
{
_service.call( "snippr.SnipprService.removeSnippet", new Responder( snippetRemoveHandler, snipprFaultHandler ), snippet_id );
trace( "Removing Snippet" );
}
public function searchSnippets( args:Array ):void
{
_service.call( "snippr.SnipprService.searchSnippets", new Responder( snippetResultHandler, snippetSearchHandler ), args );
}
/**
* We take two arguments here, one is a byte array and the other is the filename of the file
*
* @param bytes byte array
* @param filename filename of the file
*
*/
public function takeScreenshot( bytes:ByteArray, filename:String ):void
{
_service.call( "snippr.MediaService.takeScreenshot", new Responder( snapshotResultHandler, snipprFaultHandler ), bytes, filename );
trace( "Sending Screenshot" );
}
/**
* We take one argument here, and that is a email value object.
* We are passing this object to amfphp where our email will be sent
*
* @param email
*
*/
public function sendEmail( email:EmailVO ):void
{
_service.call( "snippr.MediaService.sendEmail", new Responder( emailResultHandler, snippetSavedHandler ), email );
trace( "Sending Email" );
}
/* ***********************************************************
* Result and Fault Handlers
*
* This is where all of our result and fault handling is
* going to take place, we updating the model on the results
* that we get back. Or simply displaying to the user what
* comes back to Flex.
**************************************************************/
/**
* We are handling the result coming back as an array of snippets,
* then we add our snippets to our model
*
* @param data the array of snippets
*
*/
private function snippetResultHandler( data:Array ):void
{
model.snippetCollection = new ArrayCollection( data );
}
private function snippetSearchHandler( data:Array ):void
{
trace( data );
}
/**
* We are taking the data object as the result,
* tracing it and we could display an alert to
* the user showing him/her whatever message
* we want.
*
* @param data a message showing us the status
*
*/
private function emailResultHandler( data:Object ):void
{
var result:ResultEvent = data as ResultEvent;
trace( data );
}
/**
* We are taking the data object as the result, and
* creating a new window, then adding a image inside
* the window. Then populating that image's source
* with the url that is sent back, which happens to
* be the url location where the photo is. So we just
* add the result which is a string to the source of the image
* and displying it.
*
* @param data image url
*
*/
private function snapshotResultHandler( data:Object ):void
{
var result:ResultEvent = data as ResultEvent;
Alert.show( "Nice shot, here is the link to your shot." + data, "Screenshot Saved" );
trace( data );
}
/**
* Here we are handling the result and adding it to the value of serviceResponse in our model
*
* @param data the result from amfphp
*/
private function snippetSavedHandler( data:Object ):void
{
ModelLocator.getInstance().serviceResponse = data.toString();
}
/**
* Here we are handling the result that is being returned, which will be the id of the removed snippet,
* removing it from our model, at the snippet index
*
* @param data we are just refreshing/calling for the snippets again.
*/
private function snippetRemoveHandler( data:Object ):void
{
getSnippets();
}
/**
* Here we are alerting the user that there was an error connection to our server
*
* @param fault the fault object from the call
*/
private function snipprFaultHandler( fault:Object ):void
{
Alert.show( "There was an error connecting to the server.", "Snippr Service Error" );
}
}
}
Use the following example for a reference when creating your own service proxy class for the methods that you created.
Now if you wanted to use any of these calls in your view use the following.
SnippetForm.mxml
(<)!--SnippetFormProxyService-->
(<)mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%"
creationComplete="init()"
xmlns:components="com.jonniespratley.snippr.view.components.*">
(<)mx:Script>
(<)![CDATA[
import mx.events.CloseEvent;
import mx.controls.Alert;
import com.jonniespratley.snippr.events.SnippetSaveEvent;
import mx.validators.Validator;
import com.jonniespratley.snippr.vo.SnippetVO;
import com.jonniespratley.snippr.model.ModelLocator;
/* Out Model so we can bind to the selectedSnippet */
[Bindable] public var selectedSnippet:SnippetVO;
[Bindable] private var model:ModelLocator = ModelLocator.getInstance();
/* Our validation array to hold the values of our validators */
[Bindable] private var validators:Array = new Array();
/* We alway set our validators to our validator array */
private function init():void
{
validators = [ titleV, authorV, codeV, typeV ];
}
/*
When the save button is clicked instead of sending the data right away
we first check it to see if it is indeed valid. If our validation array
is empty, then we can go ahead and send our value object to amfphp, other
wise we need to alert the user that there are some errors in the form
*/
private function checkForm():void
{
var vals:Array = new Array();
vals = Validator.validateAll( validators );
//If no errors
if ( vals.length == 0 )
{
saveSnippet();
//cleanForms();
} else {
Alert.show( "Please correct invalid form entries", "Validation Error" );
}
}
/*
The saveSnippet function that gets called when there is no errors in our form
This is one function that is going to handle both creating a new snippet, and
updating an existing one. Our server side php script says that if the snippetVO[snippet_id]
is equal to 0, then go ahead and insert it as a new snippet. But if the snippetVO[snippet_id]
is not set to 0, then update that snippet where the recieved id is equal to the id we are updating.
*/
private function saveSnippet():void
{
/* If the selectedSnippet is empty create a new snippet */
if ( selectedSnippet == null )
{
var createS:SnippetVO = new SnippetVO();
createS.snippet_id = 0;
createS.snippet_title = txt_title.text;
createS.snippet_code = txt_code.text;
createS.snippet_user = txt_author.text;
createS.snippet_type = txt_type.text;
/* Cairngorm Event */
var cEvent:SnippetSaveEvent = new SnippetSaveEvent( createS );
cEvent.dispatch();
} else {
Alert.show( "This snippet is going to be edited", "Are you Sure?", 3, null, editSnippetHandler );
}
/* Do nothing */
}
private function editSnippetHandler( event:CloseEvent ):void
{
if ( event.detail == Alert.YES )
{
/* Set the snippet id to the value of the selected snippet_id */
var updateS:SnippetVO = new SnippetVO();
updateS.snippet_id = selectedSnippet.snippet_id;
updateS.snippet_title = txt_title.text;
updateS.snippet_code = txt_code.text;
updateS.snippet_user = txt_author.text;
updateS.snippet_type = txt_type.text;
/* Cairngorm Event */
var uEvent:SnippetSaveEvent = new SnippetSaveEvent( updateS );
uEvent.dispatch();
}
//Just return
}
/* Clears all form inputs, and resets the selected index of the snippet list */
private function cleanForms():void
{
//Set the model.selectedSnippet to null, so we dont have any fields used up
selectedSnippet = new SnippetVO();
txt_title.text = "";
txt_author.text = "";
txt_code.text = "";
txt_type.text = "";
}
private function selectHandler( event:Event ):void
{
selectedSnippet = event.target.selectedItem as SnippetVO;
}
]]>
(<)/mx:Script>
(<)mx:ApplicationControlBar width="100%" styleName="formBar">
(<)mx:HBox width="100%" verticalAlign="middle">
(<)mx:Label text="Author:" fontWeight="bold"/>
(<)mx:TextInput id="txt_author"
text="{ selectedSnippet.snippet_user }"
width="100%"/>
(<)/mx:HBox>
(<)/mx:ApplicationControlBar>
(<)mx:ApplicationControlBar width="100%" styleName="formBar">
(<)mx:HBox width="100%" verticalAlign="middle">
(<)mx:Label text="Title:" fontWeight="bold"/>
(<)mx:TextInput id="txt_title"
text="{ selectedSnippet.snippet_title }"
width="100%"/>
(<)mx:Label text="Type:" fontWeight="bold"/>
(<)mx:TextInput id="txt_type"
text="{ selectedSnippet.snippet_type }"
width="100%"/>
(<)mx:Button id="btn_clear"
click="cleanForms()"
label="Clear"/>
(<)mx:Button id="btn_save"
click="checkForm()"
label="Save"/>
(<)/mx:HBox>
(<)/mx:ApplicationControlBar>
(<)mx:VBox width="100%" height="100%" label="Edit">
(<)mx:TextArea id="txt_code"
text="{ selectedSnippet.snippet_code }"
width="100%"
height="100%"
styleName="codeView"/>
(<)/mx:VBox>
(<)mx:DataGrid dataProvider="{ model.snippetCollection }"
change="selectHandler( event )"/>
(<)!-- Validators -->
(<)mx:StringValidator id="titleV"
source="{ txt_title }"
minLength="1"
maxLength="200"
required="true"
property="text"/>
(<)mx:StringValidator id="authorV"
source="{ txt_author }"
minLength="1"
maxLength="200"
required="true"
property="text"/>
(<)mx:StringValidator id="codeV"
source="{ txt_code }"
minLength="5"
required="true"
property="text"/>
(<)mx:StringValidator id="typeV"
source="{ txt_type }"
minLength="1"
maxLength="200"
required="true"
property="text"/>
(<)/mx:VBox>
SnippetList.mxml
(<)mx:VBox
xmlns:mx="http://www.adobe.com/2006/mxml"
width="200"
height="100%"
creationComplete="getSnippets()">
(<)mx:Script>
(<)![CDATA[
import mx.events.CloseEvent;
import com.jonniespratley.snippr.events.SnippetRemoveEvent;
import mx.controls.Alert;
import com.jonniespratley.snippr.vo.SnippetVO;
import com.jonniespratley.snippr.events.SnippetGetEvent;
import com.jonniespratley.snippr.model.ModelLocator;
//Make a instance of our model for our data display
[Bindable] private var model:ModelLocator = ModelLocator.getInstance();
[Bindable] public var selectedSnippet:SnippetVO;
[Bindable] private var isSelected:Boolean = false;
//Send a call to get the snippets
private function getSnippets():void
{
var evt:SnippetGetEvent = new SnippetGetEvent();
evt.dispatch();
}
//Make sure we handle the selected snippet and bind it to our model
private function selectHandler( event:Event ):void
{
selectedSnippet = event.target.selectedItem as SnippetVO;
isSelected = true;
}
private function removeSnippet():void
{
Alert.show( "Are you sure?", "Remove Snippet", 3, null, removeSnippetAlertHandler );
}
private function removeSnippetAlertHandler( event:CloseEvent ):void
{
if ( event.detail == Alert.YES )
{
var evt:SnippetRemoveEvent = new SnippetRemoveEvent( lt_snippets.selectedItem.snippet_id );
evt.dispatch();
}
}
]]>
(<)/mx:Script>
(<)!--List of Snippets-->
(<)mx:List id="lt_snippets"
dataProvider="{ model.snippetCollection }"
change="selectHandler( event )"
labelField="snippet_title"
width="100%"
height="100%"/>
(<)!--Refrest Button -->
(<)mx:Button label="Remove"
click="removeSnippet()"
enabled="{ isSelected }"
width="100%"/>
(<)/mx:VBox>
Recipe 1.8 – Sending Emails with AMFPHP
Problem: You want to be able to stay in touch with your users and to be able to do this, sending emails would be a big plus.
Solution: Create a new method in your service class that allows AMFPHP to receive and send out email. Then add the same functions to Flex where the magic happens.
We use a file called eMail.php the information about that file is provided inside the source code at the end if you want to look.
Code:
EmailService.php:
SnipprSerivce.as:
EmailVO.as
EmailForm.mxml:
Recipe 1.9 – Uploading Images with Amfphp
Problem: You want to be able to take a screenshot of your Air application because ZScreen does not capture it.
Solution: Add another method to your server side PHP script, and add the function for accessing this service in ActionScript then call this method from the view.
Code: You want to add this to your PHP file SnipprService.php:
Now add this to your ActionScript file SnipprService.as:
Now the view and you are sending screenshots Screenshot.mxml:
Recipe 1.10. PHP to ActionScript Data Types
Problem: You want to know the data type conversions of both languages.
Solution: Read a table with that info!
|
PHP |
Flash (ActionScript) |
|---|---|
|
Null |
Null |
|
Integer |
Integer |
|
Double |
Float |
|
String |
String |
|
Array (normal) |
Array |
|
Array (associative) |
Object |
|
Object |
Object |
|
Resource |
Recordset |
Here are the source files
I did this for fun, and to teach others about the benifits of using AMFPHP with Flex. A fact is that you will create Rich Internet Applications faster, more efficient and more reliable then ever before. Enjoy!




