Flex and PHP: remoting with Zend AMF
The latest PHP library to add support for AMF and remoting is Zend Framework. The preview prelease version 1.7 offers a new component Zend_AMF that lets you create Flex applications that talk to PHP backends using remoting. Since I am a big fan of remoting as a way to get data to your Flex/AIR clients, I wanted to add a short post explaining how to use it. Here is another post I wrote on remoting with AMFPHP. Actually this post is a part of a larger article I did for Adobe Developer Connection. I want to keep it more focused, so I wrote this one.
You can download a Flex Builder project that contains the code I explain in this article from here. Inside of the archive you will find a readme.txt file explaining what to do with it.
Installing the Zend Framework
After downloading the Zend Framework 1.7 archive, extract the files. Next, you have to add the library folder to your PHP include path. Open the php.ini file and add the path to the library folder to the include_path; on my machine looks like:
include_path = “c:\htdocs\zend_framework\library”
Next, save the file and restart your web server. You can read more about installing Zend Framework here. With this, you’ve completed the “installation” of Zend Framework.
What is AMF and remoting and why should you use it?
If you already know these answers, you may want to skip to the next section. Let’s start by understanding remote procedure calls. Remote procedure calls let Flex applications make direct calls on the methods of your server side classes. Using BlazeDS or LiveCycle Data Services you can expose your Java and ColdFusion classes to the Flex application. However, if you use PHP you need a third party library on the server to expose PHP classes directly. Existing solutions include Zend AMF, WebORB, and AMFPHP. This article focuses on remoting with Zend AMF. AMF is a binary protocol for serializing the messages. Because it is binary, it is more efficient in terms of bandwidth and server processing load than JSON or XML methods. If you want to see for yourself how much more efficient it is, James Ward has put together a nice benchmark.
Zend AMF is a PHP library that knows how to serialize and deserialize the AMF protocol (it is part of the Zend Framework starting with version 1.7), and thus lets you expose PHP classes to Flex applications. Another compelling reason for using remoting is code reuse. Because you can call methods on PHP classes and these methods can return PHP objects, you don’t have to modify your existing code to output JSON or XML.
As I noted earlier, Zend AMF remoting uses AMF to serialize messages between the server and Flex client. It also offers the ability to map an ActionScript class to a PHP class. For example, suppose you want to display in Flex the information from a table with the following structure:
contacts
-------------------------------
id primary key int
name varchar(255)
email varchar(255)
When using remoting, you create an ActionScript class to model this data in the client and a PHP class to model the same data on the server. When you create the PHP class that you want to call from Flex, you add a method that, for example, retrieves all the contacts from the table. This method will return an array of PHP VO classes, and in Flex you will get an array of ActionScript objects. All the conversions from PHP objects to AMF to ActionScript objects are done automatically for you by Flex and Zend AMF.
When you use XML or JSON for remoting, you’ll tipically need extra steps in Flex to process the data in order to display or store it.
Let’s look at a working example.
Create the Flex PHP 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_php”.
Next, create a folder inside the PHP server root named “zendamf_remote”, and add this folder to the project. Choose New > Folder, and then click on the Advanced button. If you want to have the source files for the Zend Framework available to your project, and you use Zend Studio too, then open the properties page for the project, go to the PHP Include Path > Libraries tab, and add an External Folder pointing to the place where the Zend Framework is installed.
Create the PHP code
In the “zendamf_remote” 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):
<?php require_once('VOAuthor.php'); //connection info define("DATABASE_SERVER", "localhost"); define("DATABASE_USERNAME", "mihai"); define("DATABASE_PASSWORD", "mihai"); define("DATABASE_NAME", "flex360"); class MyService { /** * Retrieve all the records from the table * @return an array of VOAuthor */ public function getData() { //connect to the database. //we could have used an abstracting layer for connecting to the database. //for the sake of simplicity, I choose not to. $mysql = mysql_connect(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD); mysql_select_db(DATABASE_NAME); //retrieve all rows $query = "SELECT id_aut, fname_aut, lname_aut FROM authors_aut ORDER BY fname_aut"; $result = mysql_query($query); $ret = array(); while ($row = mysql_fetch_object($result)) { $tmp = new VOAuthor(); $tmp->id_aut = $row->id_aut; $tmp->fname_aut = $row->fname_aut; $tmp->lname_aut = $row->lname_aut; $ret[] = $tmp; } mysql_free_result($result); return $ret; } /** * Update one item in the table * @param VOAuthor to be updated * @return NULL */ public function saveData($author) { if ($author == NULL) return NULL; //connect to the database. $mysql = mysql_connect(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD); mysql_select_db(DATABASE_NAME); //save changes $query = "UPDATE authors_aut SET fname_aut='".$author->fname_aut."', lname_aut='".$author->lname_aut."' WHERE id_aut=". $author->id_aut; $result = mysql_query($query); return NULL; } } ?>
This is the class you will call from Flex. It has two methods: one to get all the records from the table, and another to update the values for one 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:
<?php class VOAuthor { public $id_aut; public $fname_aut; public $lname_aut; } ?>
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 expose the MyService class to Flex clients with the help of the Zend AMF. Add the following code:
<?php require_once('Zend/Amf/Server.php'); require_once('MyService.php'); $server = new Zend_Amf_Server(); //adding our class to Zend AMF Server $server->setClass("MyService"); //Mapping the ActionScript VO to the PHP VO //you don't have to add the package name $server->setClassMap("VOAuthor", "VOAuthor"); echo($server -> handle()); ?>
I use an instance of Zend AMF server to create a PHP end point that can be called from Flex. Then I register the MyService class to the server, thus I can call this class from Flex. And finally I map the ActionScript data model (VOAuthor) to the PHP VOAuthor data model.
When you use remoting, you get the casting of the data to the right type for free. For example, MyService.getData() method returns an array of VOAuthor PHP objects. However, as you will see later, in Flex the result is an array of VOAuthor ActionScript objects.
Creating the Flex application
Now that you have the PHP code in place, you are ready to create the Flex code that will call the PHP class. I want the Flex application to have a button that gets the data from the server, uses a data grid to display the data, and enables the user to edit any cell (except ids) within the data grid. Whenever a cell is edited, the update is sent automatically to the server and saved to the database as well.
First, be sure to select the Flex perspective from the top right icons of Eclipse.
The next thing you need to do is to create a configuration file that Flex can use to reach the PHP service. Create the file services-config.xml in the root of the project. Open the file and add this code:
<?xml version="1.0" encoding="UTF-8"?> <services-config> <services> <service id="amfphp-flashremoting-service" class="flex.messaging.services.RemotingService" messageTypes="flex.messaging.messages.RemotingMessage"> <destination id="zend"> <channels> <channel ref="my-zend"/> </channels> <properties> <source>*</source> </properties> </destination> </service> </services> <channels> <channel-definition id="my-zend" class="mx.messaging.channels.AMFChannel"> <endpoint uri="http://localhost/zendamf_remote/" class="flex.messaging.endpoints.AMFEndpoint"/> </channel-definition> </channels> </services-config>
Be sure to check the endpoint node (at the bottom of the file); your URL to the zendamf_remote folder might be different. Set the value appropriately for your setup.
Now you need to tell Flex Builder to use this file when compiling the project. Right click on the project name in the Project Explorer and choose Properties. Select Flex Compiler and add the following to Additional compiler arguments field: -services “absolute_path_to_the_file/services_config.xml”:
You will use a RemoteObject to communicate with the server, so add a mx:RemoteObject tag. You need to set the source attribute to MyService (this is the PHP class name) and the destination to zend – this is the destination created in the services-config.xml file. Also give a name to this object by adding an id attribute and set it to myRemote. Set the attribute showBusyCursor to true (whenever a call is made this will render the mouse icon as a watch, until a response from the server is received). The code should look like this:
<mx:RemoteObject id="myRemote" destination="zend" source="MyService" showBusyCursor="true"> </mx:RemoteObject>
Now you need to declare the methods you want to call on the PHP class, and add the listeners for fault and result events. The code is:
<mx:RemoteObject id="myRemote" destination="zend" source="MyService" showBusyCursor="true" fault="faultListener(event)"> <mx:method name="getData" result="getDataListener(event)"/> <mx:method name="saveData" result="saveDataListener(event)"/> </mx:RemoteObject>
Next you need a UI to make the call to the server and display/edit the data. A button and a data grid will do. Add this code above the RemoteObject code:
<mx:VBox top="30" left="100"> <mx:Button label="Get data" click="{myRemote.getData()}" /> <mx:DataGrid id="myGrid" editable="true" itemEditEnd="save(event)"/> </mx:VBox>
As you can see, the button calls the getData() method on the remoteObject. The data grid has an event listener registered for the itemEditEnd event.
The last step is to create the listeners you declared. For this, add an mx:Script tag to your MXML application and define four functions in it:
<mx:Script>
<![CDATA[
import mx.controls.dataGridClasses.DataGridColumn;
import mx.events.DataGridEvent;
import org.corlan.VOAuthor;
import mx.controls.Alert;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.collections.ArrayCollection;
/**
* listener for the data grid's itemEditEnd event
*/
privatefunction save(event:DataGridEvent):void {
//we don't want to update the id of the item
if (event.dataField == "id_aut") {
event.preventDefault();
return;
}
//retrieve the new value from the item editor instance
var dataGrid:DataGrid = event.target as DataGrid;
var col:DataGridColumn = dataGrid.columns[event.columnIndex];
var newValue:String = dataGrid.itemEditorInstance[col.editorDataField];
//retrieve the data model that was edited
var author:VOAuthor = event.itemRenderer.data as VOAuthor;
// if the value wasn't change, exit
if (newValue == author[event.dataField])
return;
//update the model with the new values
author[event.dataField] = newValue;
//call the remote method passing the data we want to be saved
myRemote.saveData(author);
}
/**
* Result listener for get data operation
*/
privatefunction getDataListener(event:ResultEvent):void {
//set the result array as data provider for the data grid
myGrid.dataProvider = event.result as Array;
}
/**
* Result listener for save data operation
*/
privatefunction saveDataListener(event:ResultEvent):void {
Alert.show("The data was saved!");
}
/**
* Fault listener for RemoteObject
*/
privatefunction faultListener(event:FaultEvent):void {
Alert.show(event.fault.message, "Error");
}
]]>
</mx:Script>
Finally, you need to create the ActionScript Value Object that will act as a data model for the data sent from PHP. Right-click on the src folder from Flex Navigator, and choose New > ActionScript class. For the package type org.corlan, and for the name type VOAuthor. Click OK. Now it is time to add the members and some meta-data:
package org.corlan {
[RemoteClass(alias="VOAuthor")]
[Bindable]
publicclass VOAuthor {
publicvar id_aut:int;
publicvar fname_aut:String;
publicvar lname_aut:String;
}
}
The RemoteClass meta-data is very important. This tells to the ActionScript that the remote class (the one from PHP) that it maps to is called VOAuthor. If you forget this or you misconfigure it, you will get generic objects in ActionScript instead of VOAuthor, and associative arrays in PHP instead of VOAuthor.
You are done. There shouldn’t be any errors.
Now you are ready to test the code. Start the Flex application by clicking Run in the toolbar. When the application opens in your default browser, click the Get data button. You should see the data grid populated with some data:
To edit the items, just double click on any name and change something. When you finish editing, click outside the data grid. The changes will be sent to the server. If you don’t believe me, just go to the database and view the records.
That’s it people!
Comments
37 Responses to “Flex and PHP: remoting with Zend AMF”
Leave a Reply
[...] Flex and PHP: remoting with Zend AMF By Mihai Corlan Usually, when I work with Flex and PHP projects, I prefer to use Flex Builder and Zend Studio installed together. Though you can work with Flex Builder and some PHP plug-in to help you with the PHP code. Either way, you should create a … Mihai CORLAN - http://corlan.org/ [...]
For PHP 5.x and you can try
while ($row = mysql_fetch_object($result,”VOAuthor”)) {
$ret[] = $row;
}
My 2 cents ;).
@Radu
Thanks for pointing this!
Just what I was looking for. Thanx a lot.
Thanks! It`s great!
thanks for this good job but i have this error:
“faultCode:Client.Error.MessageSend faultString:’Send failed’ faultDetail:’Channel.Connect.Failed error NetConnection.Call.BadVersion: : url: ‘http://localhost/zendamf_remote/””
any idea to fix this?
Hi, first off great tutorial. I followed through it on the Adobe Developer Center.
I am having trouble though, I get the error that services_config.xml cannot be opened. The file is in the root director of the project and this has been correctly added to the flex compiler options per your turorial. My path is correct, and the file does exist in the exact location on the filesystem. This is on Vista. Any ideas?
One other question - is there a way to update Zend Studio to use ZF 1.7, so that we can just create a “Zend Framework Project” instead of “PHP Project” and still get Zend AMF for Flex?
Thanks!
Jeremy
@joox
Man, I really couldn’t tell without having access to your server. The only idea I have:
- see if it is running in the browser http://localhost/zendamf_remote
if you get errors, then probably you either didn’t configure zend framework or you have some typos in the php
@Jeremy Savoy
regarding your first issue, are you on Mac or win; most probably either the path isn’t correct or is some type in the xml file
regarding the upgrading the zend framework, just go to Window > Preferences, then select from the left tree PHP > PHP variables and then add new framework in the list
Mihai, thanks for responding!
I’m on Vista. The path is correct, the file is absolutely at the path I have set, and I’ve only copied the XML directly from your tutorial.
I do get an XML warning, but not sure how that would affect it …
No grammar constraints (DTD or XML schema) detected for the document. services-config.xml
@Mihai, thanks for your answer, i’v cheked my server and php and i fixed my problem but now i have this error
“faultCode:Client.Error.DeliveryInDoubt faultString:’Channel disconnected’ faultDetail:’Channel disconnected before an acknowledgement was received’”
@joox
I have a wild quess that maybe some of your PHP still has a problem. Could be a space after the PHP closing tag (?>) or something like this. Other than that I don’t have any idea
@Jeremy
I really don’t know what to say
It should work. Is your PHP method returning something?
Ok, interestingly enough I tried the same example on a different computer today and had no errors, everything works as expected except that I had to manually copy the Zend/ directory to zendamf_remote, otherwise index.php gave errors about not finding Zend/Amf/Server.php …. and I did set up my PHP Includes so not sure what I missed there. Thanks again for the tutorial!
@Jeremy
I think you didn’t install the Zend framework into your php.ini file. maybe the path is wrong, or you forgot to add, or to restart the server.
that’s really strange with working on one machine and not on another. I suspect is a problem with PHP version or configuration.
Dear joox and Dear Mihai corlan
I had problem same joox but I founded reason of following Error :
“faultCode:Client.Error.DeliveryInDoubt faultString:’Channel disconnected’ faultDetail:’Channel disconnected before an acknowledgement was received’”
it is simple , the connection to mysql failed.
solution: check the connection parameter in MySrvice.php (define(”DATABASE_SERVER”, “localhost”);
define(”DATABASE_USERNAME”, “root”);
define(”DATABASE_PASSWORD”, “”);
define(”DATABASE_NAME”, “flex_php”);
for connecting to mysql , also check your database structure such as table name , field name (id_aut, fname_aut, lname_aut )
to be same in MySrvice.php file.
it was simple but i have spent 2 days for it.
in the end I really appreciate for tutorial Mr. Mihai Corlan.
BR
farid valipour
@farid valipour
thank you!
@all
generally the first thing you should do when you get errors in Flex, try to execute the PHP files in the browser (executes the methods you are calling from Flex), with show errors on; thus you will find easy trivial errors.
hi everyone!
well, it took me some time
but i’ve managed to get everything to work locally. i did actually find most of the mistakes by checking the methods in the browser. thanks for a great advice!
now, i’ve uploaded the site. the server doesn’t return any errors but when i point my browser to mysite.com/zendamf_remote , the browser asks me if i want to download a file which actually contains the following text:
Zend Amf Endpoint
if i comment out:
// echo($server -> handle());
“Zend Amf Endpoint” is returned in my browser window, the way it does when testing locally!!!
So, php seems to be fine since all is working locally…
On the web server, I’m connected to Zend Amf Server, since the browser returns the endpoint address…
In order to avoid the Security sandbox violation, i have set the endpoint uri in the services-config.xml to zendamf_remote/ and this works fine locally…
So it seems that all the pieces are in place and yet something’s missing!!! Any ideas???
Help!
Thanks to all,
Patrick
the endpoint uri in th
@patrick segarel
If I hit zend_amf in a browser, I am too prompt to save the file. This is not a problem, it is the normal behavior.
So what is the error you get in Flex?
I am not sure why should you get a sandbox violation, as the Flex app is loaded from the same domain where RemoteObject is.
thanks for your quick reply
I don’t get a specific error, but I don’t get any data! If I test my service class directly, by calling it in the browser the data is returned though.
I got a sandbox violation error when trying to test the services-config.xml, with the web address…but i can’t reproduce this actually.
So, I guess the real question is, what are the steps to change from a local environment to a web environment?
As mentioned before, the php code seems fine, as it’s working locally. My database connection is fine, I can test it in the browser and get the data back, the connection to the zend_amf_server is fine too… i’ve now changed the address in the services-config.xml to my site’s endpoint address.
don’t know what else to do…
patrick
Hi Mihai,
First thx for your tutorial !
after testing successfully your sample, I’m tryin’ now to use the Zend_Mail_Storage_Pop3 class to retrieve some mails from a POP3 server.
I have some basic and simple classes which work fine in the browser when I test them, but get that “Channel disconnected before an acknowledgement was received” error when I’m testing with Flex…
I can’t find out where is the problem, cause everything seems right on the php side (so in Flex ).
Would you have a little bit of time to have a glance on my files, and if so, where could I post them ?
Thx
@quantum
Unfortunately I can’t take a look at your files. If I will do this for every people who ask my help, I wouldn’t be able to right any article
Your error it could be triggered by some output from a file before the AMF stream is output (such as newlines after the closing php tags and so on).
thank you for your kind words, and sorry I can’t do more for you.
Ok Mihai,
I understand, thx anyway for responding and have good times at Milan
Ditto to Patrick’s comments: I’ve got the BadVersion blues. My analysis:
With MAMP (as on Lee Brimelow’s excellent gotoandlearn.com tutorial), I have everything fine on the localhost:8888 setting. On my Mac OSX10.5, with Zend 1.7 and CS4, I get exactly what I’m expecting. When I move to the server, I see the Zend Amf Endpoint message; but from with the SWF, I get the BadVersion error. Any ideas? It sounds the same as Patrick’s.
it’s all working fine now! the main problem was with the services-config.xml.
i can’t remember what steps i took to move my project to the web, but i must have done something in the wrong order… now that all is running fine, it seems that the only thing I needed to do (that is, after setting up the server on my site) was to change the endpoint uri in the services-config file!
thanks again for your tutorial!
[...] http://corlan.org/2008/11/13/flex-and-php-remoting-with-zend-amf/ [...]
It’s great that Zend and Adobe have worked together to get an AMF implementation to PHP. However, I can’t find anything on how to secure the remoting services with PHP as you can do with ColdFusion and BlazeDS. Any suggestions?
@Ron Ferguson
You can make the calls over HTTPS. is this enough or your question was different?
I was thinking a different kind of security. In ColdFusion, you can secure CFC methods with usernames and passwords. I was looking for this kind of authentication in Zend_Amf but can’t find anything on it. Hope this clears it up a little.
@Ron Ferguson
Zend Framework has support for access control list (ACL). Just have a look at the online documentation:
http://framework.zend.com/manual/en/zend.acl.html
I didn’t try to use it with Zend AMF.
I just looked over the Zend_ACL docs. This looks comparable to the CFC way of doing it. I’ll give it a try.
Very useful thanks, God bless you
As an alternative to setting up the Zend AMF server directly inside the bootstrap file, I’d rather recommend to set it up inside a normal Zend action controller. This gives you the flexibilty of adding an HTML interface to your app if needed. Also, it makes it easier to utilize other features of ZF (like Zend_Db, Zend_Config, etc.). I wrote a mini tutorial on how to do this:
http://blog.log2e.com/2008/12/14/utilizing-the-zend-amf-server-inside-a-zend-controller/
[...] Zend Framework and Adobe Rich Media: http://corlan.org/2008/11/13/flex-and-php-remoting-with-zend-am... [...]
Hi guys
I have extended this sample with cairngorm micro- architecture to be adobe standard and for starting real projests with flex and php based zendamf and cairngorm.
I’ll prepare the video tutorial for this too.
you can download the project source in http://www.ria.webandishan.com/Flex-ZendAmf-Cairngorm-Cairngen.rar
if you had comments pls let me know about it.
think’s for this it was usefull, I tested with my framework zend with a specific amf view
would be interesting to see how manage errors of php (exceptions in zend framework) in flex
and what is the best design with MVC Zend
your opinion for best design between flex and zend ?
I used your tutorial (think’s to you ! ^^) with “just” adding specific public directory and index, bootstrap and layout files for for AMF, and a specific AMF view in controller which handle the server
so I keep the MVC model and I think I have just now to add AMF empty actions to my controllers wich AMF view which put the remoting classes to flex
and I call components in flex by calling the AMF actions of controllers
I think it respects the MVC model, but in this case view is not very useful (it just contains the Zend_Amf_Server code)
do you think it’s good ? or do you advise a best practice ?
it’s for a professionnal website so I want to make the best design
another question for a professionnal using is how flex can manage the exceptions classes we create un Zend (for example if data zend components create a specific exception because it can’t access datas)
think’s in advance
and good christmas from Paris !