Flex and PHP: remoting with SabreAMF

In the past months I have written a lot about Flex and PHP remoting using AMFPHP, ZendAMF, and WebORB. However, there is another library, SabreAMF, that can be used to do remoting. This article explains how to use it.

If you don’t know what remoting is (or AMF), then it might be a good idea to read my earlier post first (look for the “What is AMF and remoting and why should you use it” section).

Installing SabreAMF

You can download the library from here, and here you can find some documentation. I will say upfront, that indeed, compared to the other three PHP libraries for remoting, SabreAMF offers less documentation.

Unzip the archive somewhere on your hard-disk. You have two alternatives for using it with your PHP code:

  1. You can add the library to your PHP include_path in your php.ini file. To do this you need to create a folder, called for example “mysabre”, and in this folder create a folder called “SabreAMF”. In this last folder copy all the files from the archive (AMF0, AMF3, examples, and so on). Next, open the php.ini file and search for the line include_path and add the absolute path to the folder mysabre. On my machine, this looks like this:
    include_path = “/frameworks/mysabre”
  2. Alternatively, you can create a folder called “SabreAMF” inside of your PHP working directory, and copy to this folder all the files and folders from the archive.

You are now ready to move to the PHP code you need for this example.

Create the Flex project

Usually, when I work with Flex and PHP projects, I prefer to use Flex Builder and Zend Studio installed together. It is possible, however, to work with Flex Builder and a PHP plugin to help you with the PHP code. Either way, you should create a Flex project that uses PHP on the server side (if you plan to use Zend Studio and Flex Builder, first create a Zend PHP Project, then use the Add Flex Nature wizard to add Flex PHP nature on the project). This way you streamline the deployment of the SWF file (the compiled result of the Flex project) to the PHP server. I chose to create a new project called “flex_sabre”.

Next, create a folder inside the PHP server root named “sabreamf_remoting”, and add this folder to the project. Choose New > Folder, and then click on the Advanced button.

Create the PHP code

The example will use a PHP class to manage one table from a MySQL server.

In the “sabreamf_remoting” folder, create three PHP files: MyService.php, VOAuthor.php, and index.php. Open the MyService.php page and paste the following code (you need to update the connection information for your specific database setup; to do this, look for the four constants at the top of the class):

   1: <?php
   2: require_once 'VOAuthor.php';
   3:
   4: //conection info
   5: define("DATABASE_SERVER", "localhost");
   6: define("DATABASE_USERNAME", "mihai");
   7: define("DATABASE_PASSWORD", "mihai");
   8: define("DATABASE_NAME", "flex360");
   9:
  10: //$o = new MyService();
  11: //print_r($o->getData());
  12:
  13: class MyService {
  14:
  15:     /**
  16:      * Retrieve all the records from the table
  17:      * @return an array of VOAuthor
  18:      */
  19:     public function getData() {
  20:         //connect to the database.
  21:         //we could have used an abstracting layer for connecting to the database.
  22:         //for the sake of simplicity, I choose not to.
  23:         $mysql = mysql_connect(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD);
  24:         mysql_select_db(DATABASE_NAME);
  25:         //retrieve all rows
  26:         $query = "SELECT id_aut, fname_aut, lname_aut FROM authors_aut ORDER BY fname_aut";
  27:         $result = mysql_query($query);
  28:         //throw (new Zend_Amf_Exception('eroare ca asa vreau eu', 11));
  29:         $ret = array();
  30:         while ($row = mysql_fetch_object($result, "VOAuthor")) {
  31:             $ret[] = $row;
  32:         }
  33:         mysql_free_result($result);
  34:         return $ret;
  35:     }
  36:         /**
  37:      * Update one item in the table
  38:      * @param VOAuthor to be updated 
  39:      * @return NULL
  40:      */
  41:     public function saveData($author) {
  42:         if ($author == NULL)
  43:             return NULL;
  44:         //connect to the database.
  45:         $mysql = mysql_connect(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD);
  46:         mysql_select_db(DATABASE_NAME);
  47:         if ($author->id_aut > 0) {
  48:             //save changes
  49:             $query = "UPDATE authors_aut SET fname_aut='".$author->fname_aut."', lname_aut='".$author->lname_aut."' WHERE id_aut=".  $author->id_aut;
  50:         } else {
  51:             //add new record
  52:             $query = "INSERT INTO authors_aut (fname_aut, lname_aut) VALUES ('".$author->fname_aut."', '".$author->lname_aut."')";
  53:         }
  54:         $result = mysql_query($query);
  55:         return NULL;
  56:     }
  57:
  58:     public function deleteData($author) {
  59:         if ($author == NULL)
  60:             return NULL;
  61:
  62:         //connect to the database.
  63:         $mysql = mysql_connect(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD);
  64:         mysql_select_db(DATABASE_NAME);
  65:         //delete a record
  66:         $query = "DELETE FROM authors_aut WHERE id_aut = ".$author->id_aut;
  67:         $result = mysql_query($query);
  68:         return NULL;
  69:     }
  70:
  71:     public function setCredentials($user, $password, $charset) {
  72:         return true;
  73:     }
  74: }
  75:
  76: ?>

This is the class you will call from Flex. It has three methods: one to get all the records from the table, another to update the values for one record, and the third to delete a record.

Let’s create the code for the Value Object, the data model. This is used by the MyService class to wrap one row from the table. Thus, the method getData() returns an array of VOAuthor, and the method saveData() receives one argument: the VOAuthor of the row that was changed. Open the file VOAuthor.php and add this code:

   1: <?php
   2: class VOAuthor {
   3:     public var $id_aut;
   4:     public var $fname_aut;
   5:     public var $lname_aut;
   6: }
   7: ?>

As you can see, this class is very simple; it just provides the same members as the fields from the table. Finally let’s create the code for index.php file. This is the plumbing code that exposes the MyService class to Flex clients with the help of SabreAMF. Add the following code:

   1: <?php
   2: require_once 'SabreAMF/CallbackServer.php';
   3: require_once 'MyService.php';
   4: require_once 'VOAuthor.php';
   5:
   6: function myCallback($serviceName, $methodName, $arguments) {
   7:     // You can use the servicename to lookup and instantiate a service class
   8:     $serviceObject = new $serviceName;
   9:     if (!is_array( $arguments)) {
  10:         $arguments = array($arguments);
  11:     }
  12:     // check the php manual if you don't know what this function does
  13:     return call_user_func_array(array($serviceObject, $methodName), $arguments);
  14: }
  15:
  16: //map the AS VO to PHP VO
  17: SabreAMF_ClassMapper::registerClass('org.corlan.VOAuthor','VOAuthor');
  18:
  19:
  20: // First we'll create the server object
  21: $server = new SabreAMF_CallbackServer();
  22: // setup the callback
  23: $server->onInvokeService = 'myCallback';
  24: // parse the request and spit out the response
  25: $server->exec();
  26: ?>

And this is the part where you have to pay attention. Chances are that if you encounter problems later, the root of the problems is in this file. First of all, you use an instance of SabreAMF_CallbackServer to set up the server that knows how to handle the remoting calls from Flex. Next, you add a PHP function as the callback for the onInvokeService property of the SabreAMF_CallbackServer. This function will be called each time the PHP server receives a remote call. Next, you define the callback function. In my code, the function is called myCallback.

You might wonder why you need to do this, and why it is not done automatically for you by SabreAMF. Indeed, it would have been very easy to have a default function registered for you, so you don’t have to do this copy & paste step. However, there is a reason: you can write your own function and you can handle different calls in different ways. In other words, it is easy to customize how a specific remote call should be handled by your code. If you are not satisfied with this answer, please let me hear your opinion :D

And finally, you use the static method registerClass(‘package.remoteclass,’localclass’) on SabreAMF_ClassMapper object to tell PHP which ActionScript VO corresponds to the PHP VO.

And now, you are ready to move to the client code.

Creating the Flex application

This is very simple code. You have the MXML file with a RemoteObject to do the calls to the PHP class. Then you have a data grid to display and edit the existing records, and two buttons: one for save and one to get the data. Next there are a bunch of functions, some are listeners for various events (result event or fault event on the remote object, change item on the data grid). Here is the code:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
   3:     creationComplete="init()" >
   4: <mx:Script>
   5:     <![CDATA[
   6:         import mx.controls.Alert;
   7:         import mx.rpc.events.FaultEvent;
   8:         import org.corlan.VOAuthor;
   9:         import mx.events.DataGridEvent;
  10:         import mx.rpc.events.ResultEvent;
  11:
  12:         private var item:VOAuthor;
  13:
  14:         private function init():void {
  15:             myRPC.getData();
  16:         }
  17:         private function getDataListener(event:ResultEvent):void {
  18:             dg.dataProvider = event.result;
  19:         }
  20:         private function editListener(event:DataGridEvent):void {
  21:             item = event.itemRenderer.data as VOAuthor;
  22:         }
  23:         private function saveDataListener(event:ResultEvent):void {
  24:             item = null;
  25:             init();
  26:         }
  27:         private function saveData():void {
  28:             if (item == null)
  29:                 return;
  30:             myRPC.saveData(item);
  31:         }
  32:         private function faultListener(event:FaultEvent):void {
  33:             Alert.show(event.fault.faultString);
  34:         }
  35:
  36:     ]]>
  37: </mx:Script>
  38:
  39: <mx:RemoteObject id="myRPC" destination="MyService" endpoint="http://localhost/sabreamf_remoting/index.php" source="MyService" fault="faultListener(event)">
  40:     <mx:method name="getData" result="getDataListener(event)"/>
  41:     <mx:method name="saveData" result="saveDataListener(event)"/>
  42: </mx:RemoteObject>
  43: <mx:VBox>
  44:     <mx:DataGrid id="dg" editable="true" itemEditEnd="editListener(event)" />
  45:     <mx:Button label="Save" click="saveData()" />
  46:     <mx:Button label="Get Data" click="init()" />
  47: </mx:VBox>
  48:
  49: </mx:Application>

And finally, you have to create the ActionScript Value Object, org.corlan.VOAuthor:

   1: package org.corlan
   2: {
   3:     [Bindable]
   4:     [RemoteClass(alias="org.corlan.VOAuthor")]
   5:     public class VOAuthor
   6:     {
   7:         public var id_aut:int;
   8:         public var fname_aut:String;
   9:         public var lname_aut:String;
  10:     }
  11: }

With this object, you must pay a little bit of attention. Normally, the value for the alias attribute should be VOAuthor (as the PHP class doesn’t have a package name). However, if you want receive typed objects in Flex and not anonymous objects, you have to put the same value as the fully qualified class name itself (and this is the value you set up in the index.php file too).

Conclusion

That’s it. You can download the project from here. I will write an article soon that compares the four available libraries for PHP and Flex remoting.

4 thoughts on “Flex and PHP: remoting with SabreAMF

  1. 65: //add new record
    66: $query = “DELETE FROM authors_aut WHERE id_aut = “.$author->id_aut;
    Right…

    Also, please use sprintf + mysql_real_escape_string instead of concatenating the query string with the parameters. It’s examples like these that make people oblivious to SQL injection vulnerabilities.

    So, instead of
    $query = “UPDATE authors_aut SET fname_aut='”.$author->fname_aut.”‘, lname_aut='”.$author->lname_aut.”‘ WHERE id_aut=”. $author->id_aut;

    you can write:
    $query = sprintf(“UPDATE authors_aut SET fname_aut=’%s’, lname_aut=’%s’ WHERE id_aut=’%s'”, mysql_real_escape_string($author->fname_aut), mysql_real_escape_string($author->lname_aut), mysql_real_escape_string($author->id_aut));

    Safer, maintainable and easier to read.

  2. @mrm

    Thanks for your comment.

    My intention was to keep the number of lines, especially in the PHP side at a minimum. This blog post is not about how to handle a table in PHP. I assume people are already familiar with this subject. Thus no SQL escaping, no database access layer (Pear DB/ADODB/etc).

    But I have to agree with you, it is a bad idea to let in production code that doesn’t protect you of SQL injection.

  3. Pingback: Flex and remoting with PHP, which library is the best: Zend AMF, AMFPHP, WebORB for PHP, or SabreAMF? : Mihai CORLAN

  4. Hey,
    Great article (the only one in the world), i discovered php/flex only now i ever used pear, sabre is a nice solution. Where can i find more documentation about the flex aspect because there are no example about this library. I’d like to build a simple example that non return an array of obj but a simple value (true/false) or an integer is possible with this library or i have to wrap everything in a object.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>