Quick poll on debugging and tooling for Flex and PHP projects
As you know, one of my focuses as a Platform evangelist is Flex and PHP integration. Thus you can imagine I spend quite a lot of time doing Flex and PHP projects or research around these technologies. Lately I’ve been working on workflows for PHP and Flex (tooling, debugging, libraries) in light of the new tools (or new versions) we have been developing at Adobe. While some of these findings will see the light as articles, others are more intended as suggestions or feature request for upcoming versions of Flash Builder.
I’m really curious to find out:
- What tools are you using?
- What do you use for debugging?
- What frameworks (AMFPHP, Zend Framework, Doctrine etc) do you use when working on Flex and PHP projects?
- And the most important question: What features do you think Flash Builder needs in order to make you a happier/richer developer?
Please take some time and drop a comment with your thoughts around these questions.
And because I understand it takes some time to answer these questions, I want to give away three Flash Builder 4 licenses. I will choose randomly three lucky people from those who take the survey. The winners will be announced at the end of March, however you’ll get the licenses once we release Flash Builder 4 (now it is in Beta 2). Make sure you fill in your real name and a valid email address.
FlexPMD Eclipse plug-in available
We just released FlexPMD 1.0, FlexCPD 1.0, FlexMetrics 1.0, and an Eclipse FlexPMD plug-in for Flash Builder (beta for now).
If you haven’t tried FlexPMD, then you should download the plug-in and test it.
FlexPMD is a tool that helps to improve code quality by auditing any AS3/Flex source directory and detecting common bad practices, such as:
* Unused code (functions, variables, constants, etc.)
* Inefficient code (misuse of dynamic filters, heavy constructors, etc.)
* Over-complex code (nested loops, too many conditionals, etc.)
* Over-long code (classes, methods, etc.)
* Incorrect use of the Flex component lifecycle (commitProperties, etc.)
FlexCPD detects copy & pasted code snippets (it reuses the JavaCPD engine).
You can see FlexPMD in action in these two screencasts.
Webinar: using PHP and Flash for developing Rich Internet Applications
On December 2nd, together with Roy Ganor from Zend, I will host an e-seminar about PHP and the Flash Platform. We will show you how you can create a Rich Internet Application using the Flex framework, Illustrator, Flash Catalyst, and Flash Builder 4. Then we will show you how to connect the Flex application to a PHP backend and how easy is to debug the PHP and Flex code using Zend Studio 7.1 and Flash Builder 4.
You can register for free here. The webinar will start at 6:00 PM Central European Time (9:00AM Pacific Standard Time) on December 2nd.
LATER UPDATE:
You can download the slides from here, and next week the recording will be available you can watch the recording over here (you need to have a Zend account).
PHP and Flex Webinars
Last week I visited Zend headquarters, and I had an interesting talk over there. One effect of this meeting is this: we will start to do webinars together with Zend.
If you want to find more about Zend Studio and other products related to PHP from Zend, or learn more about the integration between the Flash Platform and PHP (Flash Catalyst, Flash Builder, Flex framework) you shouldn’t miss this opportunity. I know that webinars don’t offer the same experience as in-person events. On the other hand, you can attend them from the comfort of your own home, there is no traveling involved and no need to convince your boss to let you attend the event.


We haven’t set the first event date yet, but it should be in the first week of December, and probably it will be in the evening (Central European Time).
Keep an eye on my blog for the exact date and time.
Adobe Flash Builder for Force.com Preview
We just launched a developer preview for Flash Builder for Force.com; it allows you to create Flex applications that connect to services and data in the cloud (Force.com platform). You can read more here.
Screencast on data paging with Flash Builder and PHP
Not so long ago I wrote an article on how to enable data paging for a data grid using Flash Builder and a PHP backend. Today, I added a screencast for those of you who prefer to watch instead of read.
Enjoy!
Adobe User Group Tour
As you probably know, we launched the first public betas for Flash Builder 4, Flash Catalyst, and Flex SDK 4 in June 2009. And we had about 100 events across the world, events that were driven by the communities (Adobe User Groups). I had the pleasure to present at four events: Amsterdam, Netherlands; Bucharest, Romania; Zagreb, Croatia; and Pavia, Italy.
I think all the events went great, and personally I had great fun doing the presentations and talking with the people afterwards. But, from my point of view the Adobe User Group from Netherlands deserves a special mention. Somehow the Netherlands, of all the events I’ve attended so far, has something special that is hard to find in other places. And I am not talking only about the Adobe User Groups events, I found that this is true also for events organized by the PHP community for example.
The location was extraordinary, the audience was big (more than 200 people) and very eager to ask questions, and there was good food and cold beverages, including after hours beer. I can’t stop thinking how it would be to have a MAX event in Amsterdam
The organizers taped the event. So, in case there wasn’t a Tour event near you, you can see here the presentations Christoph Rooms and I gave on Flash Catalyst, Flex SDK 4, and Flash Builder 4.
Data Paging with Flex and PHP in Flash Builder
Flash Builder 4 can really speed up the development of data-driven applications. There are new wizards that can help you create the data service and the data model on the client side, and map client side operations to server side operations (CRUD applications). This post is an introduction to the new Data/Services view from Flash Builder 4 (I am using a PHP server and a PHP class).
In this article I will focus on how to use the available wizards to do data paging. While this new feature can handle ColdFusion, Java, REST style services, and SOAP services, in this post I will use PHP. Data paging is very useful when you have large sets of data. Basically, using this feature, you can bind a data grid to a server operation that manages a large table, and only 20 records will be sent to the client at one time. As you scroll through the data grid and reach areas where you don’t have data yet, a new request is made for that region and another page of records is brought into the client.
PHP code
On the server side I have two classes. One is the data model for my data, VOEmployee:
1: <?php
2: class VOEmployee {
3:
4: public $emp_no;
5: public $birth_date;
6: public $first_name;
7: public $last_name;
8: public $gender;
9: public $hire_date;
10: public $phone_no;
11: public $email_address;
12: public $job_title;
13: }
14: ?>
This class wraps a record from the MySQL table. The second class is the PHP service itself, Employees.php.
1: <?php
2: require_once 'VOEmployee.php';
3:
4: //conection info
5: define("DATABASE_SERVER", "localhost");
6: define("DATABASE_USERNAME", "mihai");
7: define("DATABASE_PASSWORD", "mihai");
8: define("DATABASE_NAME", "employees");
9:
10: /**
11: $o = new Employees();
12: $o->count();
13: */
14:
15: class Employees {
16:
17: private $_con;
18:
19: public function Employees() {
20: $this->_con = mysql_connect(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD);
21: if (!$this->_con)
22: $this->throwError();
23: mysql_select_db(DATABASE_NAME);
24: }
25:
26: public function getAllItems($lastName) {
27: if ($lastName==NULL) {
28: $sql = 'SELECT * FROM employees';
29: } else {
30: $sql = 'SELECT * FROM employees WHERE last_name LIKE \'%'.mysql_escape_string($lastName).'%\'';
31: }
32: $result = mysql_query($sql, $this->_con);
33: if (!$result)
34: $this->throwError();
35: $ret = array();
36: while ($row = mysql_fetch_object($result, "VOEmployee")) {
37: $ret[] = $row;
38: }
39: mysql_free_result($result);
40: return $ret;
41: }
42:
43: public function getItem($itemID) {
44: $sql = 'SELECT * FROM employees WHERE emp_no = '.mysql_escape_string($itemID);
45: $result = mysql_query($sql, $this->_con);
46: if (!$result)
47: $this->throwError();
48: $ret = mysql_fetch_object($result, "VOEmployee");
49: mysql_free_result($result);
50:
51: return $ret;
52: }
53:
54: public function createItem($item) {
55: $sql = 'INSERT INTO employees (first_name, last_name, gender, email_address, hire_date, birth_date, job_title) VALUES (';
56: $sql .= '\'' . mysql_escape_string($item->first_name) . '\', ';
57: $sql .= '\'' . mysql_escape_string($item->last_name) . '\', ';
58: $sql .= '\'' . mysql_escape_string($item->gender) . '\', ';
59: $sql .= '\'' . mysql_escape_string($item->email_address) . '\', ';
60: $sql .= '\'' . mysql_escape_string($item->hire_date) . '\', ';
61: $sql .= '\'' . mysql_escape_string($item->birth_date) . '\'';
62: $sql .= ')';
63: $result = mysql_query($sql, $this->_con);
64: if (!$result)
65: $this->throwError();
66: return NULL;
67: }
68:
69: public function updateItem($item) {
70: $sql = 'UPDATE employees SET ';
71: $sql .= 'first_name = \'' . mysql_escape_string($item->first_name) . '\', ';
72: $sql .= 'last_name = \'' . mysql_escape_string($item->last_name) . '\', ';
73: $sql .= 'gender = \'' . mysql_escape_string($item->gender) . '\', ';
74: $sql .= 'email_address = \'' . mysql_escape_string($item->email_address) . '\', ';
75: $sql .= 'hire_date = \'' . mysql_escape_string($item->hire_date) . '\', ';
76: $sql .= 'birth_date = \'' . mysql_escape_string($item->birth_date) . '\', ';
77: $sql .= 'job_title = \'' . mysql_escape_string($item->job_title) . '\'';
78: $sql .= ' WHERE emp_no = ' . mysql_escape_string($item->emp_no);
79:
80: $result = mysql_query($sql, $this->_con);
81: if (!$result) {
82: $this->throwError();
83: }
84: return NULL;
85: }
86:
87: public function deleteItem($itemID) {
88: $sql = 'DELETE FROM employees WHERE emp_no = ' . mysql_escape_string($itemID);
89: $result = mysql_query($sql, $this->_con);
90: if (!$result)
91: $this->throwError();
92: return NULL;
93: }
94:
95: public function count() {
96: $sql = 'SELECT COUNT(emp_no) AS empCount FROM employees';
97: $result = mysql_query($sql, $this->_con);
98: if (!$result)
99: $this->throwError();
100: $row = mysql_fetch_object($result);
101: $ret = $row->empCount + 0;
102: mysql_free_result($result);
103: return $ret;
104: }
105:
106: public function getItems_paged($startIndex, $numItems) {
107: $sql = 'SELECT * FROM employees LIMIT ' . mysql_escape_string($startIndex) . ', ' . mysql_escape_string($numItems);
108: $result = mysql_query($sql, $this->_con);
109: if (!$result)
110: $this->throwError();
111:
112: $ret = array();
113: while ($row = mysql_fetch_object($result, "VOEmployee")) {
114: $ret[] = $row;
115: }
116: mysql_free_result($result);
117: return $ret;
118: }
119:
120: private function throwError($message="") {
121: if ($this->_con)
122: $message .= 'Error no: ' . mysql_errno($this->_con) . '. Message: ' . mysql_error($this->_con);
123: throw new Exception($message, 1);
124: }
125: }
126:
127:
128: ?>
While this service implements all the CRUD operations, actually I’ll use only two methods for my example: getItems_paged() and count().
Flex code
On the client, I created a Flex project that uses PHP as the server side technology, and then I added a data grid.
1: <?xml version="1.0" encoding="utf-8"?>
2: <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/halo" minWidth="1024" minHeight="768">
3: <mx:DataGrid x="42" y="40">
4: <mx:columns>
5: <mx:DataGridColumn headerText="Column 1" dataField="col1"/>
6: <mx:DataGridColumn headerText="Column 2" dataField="col2"/>
7: <mx:DataGridColumn headerText="Column 3" dataField="col3"/>
8: </mx:columns>
9: </mx:DataGrid>
10: <s:layout>
11: <s:BasicLayout/>
12: </s:layout>
13: </s:Application>
Next, using the Data/Services panel you can add a new data service (choose PHP from the wizard’s first page), and point the wizard to the PHP class Employees.php. After completing this step, you need to define the Return Type for the getItems_paged() method. Right-click on the method and choose Return type. Next add a name (Employee) and make sure you select the int type for the two arguments. Finally set some values for these two arguments 1 and 20, for example.
All these steps I’ve already explained in detail in my previous post. Now let’s add the paging capabilities. Right-click on the getItems_paged() method from the Data/Services view, and choose from the menu Enable Paging…. In the first page of the wizard select the primary key property of the Employee data model:
And, in the second page select the count method from the dropdown (this step is optional; if you don’t specify a count() method, then when the data grid is rendered the height of the scrollbar’s thumb is not correlated with the number of the records. Usually the more data you have, the thinner the thumb will be):
Click Finish, and then bind the getItems_paged() operation to the data grid (Go to Design mode, select the operation from the Data/Services view and drag-and-drop over the data grid). And actually this is all you have to do. This is how the Employees data services looks:
When you run the application, it should look like this:
Changing the Page size
You’ve probably noticed that the wizard doesn’t provide any means to control the size of the page. And if you take a look at the Network Monitor and you inspect the result, you’ll see that the page size is set to 20 records.
If you want to change the size, you can do it programmatically using the property pageSize of the DataManager class (you can see the doc for this class here). And you can obtain the DataManager instance for your service using the getDataManager() method on the service. The code looks like this:
1: var d:DataManager = employees.getDataManager(employees.DATA_MANAGER_EMPLOYEE);
2: d.pageSize = 10;
3:
4: [...]
5:
6: <employees:Employees id="employees" destination="Employees"
7: endpoint="http://localhost/dataPaging-debug/gateway.php"
8: fault="Alert.show(event.fault.faultString)"
9: showBusyCursor="true" source="Employees"/>
What could go wrong
If you are not able to add data paging to your data service, the PHP method probably doesn’t have the correct number of arguments or the arguments don’t have the expected names (please don’t ask me why it has to be this way). Here is the error message:
Secondly, when you run your application you could see this error:
1: ArgumentError: null
2: at mx.data::RPCDataServiceAdapter/countQueryResult()[C:\depot\DataServices\branches\dune_beta1\frameworks\projects\data\src\mx\data\RPCDataServiceAdapter.as:1426]
3: at mx.collections::ItemResponder/result()[E:\dev\beta1\frameworks\projects\framework\src\mx\collections\ItemResponder.as:129]
4: at mx.rpc::AsyncToken/http://www.adobe.com/2006/flex/mx/internal::applyResult()[E:\dev\beta1\frameworks\projects\rpc\src\mx\rpc\AsyncToken.as:239]
5: at mx.rpc.events::ResultEvent/http://www.adobe.com/2006/flex/mx/internal::callTokenResponders()[E:\dev\beta1\frameworks\projects\rpc\src\mx\rpc\events\ResultEvent.as:207]
6: at mx.rpc::AbstractOperation/http://www.adobe.com/2006/flex/mx/internal::dispatchRpcEvent()[E:\dev\beta1\frameworks\projects\rpc\src\mx\rpc\AbstractOperation.as:244]
7: at mx.rpc::AbstractInvoker/http://www.adobe.com/2006/flex/mx/internal::resultHandler()[E:\dev\beta1\frameworks\projects\rpc\src\mx\rpc\AbstractInvoker.as:318]
8: at mx.rpc::Responder/result()[E:\dev\beta1\frameworks\projects\rpc\src\mx\rpc\Responder.as:58]
9: at mx.rpc::AsyncRequest/acknowledge()[E:\dev\beta1\frameworks\projects\rpc\src\mx\rpc\AsyncRequest.as:84]
10: at NetConnectionMessageResponder/resultHandler()[E:\dev\beta1\frameworks\projects\rpc\src\mx\messaging\channels\NetConnectionChannel.as:538]
11: at mx.messaging::MessageResponder/result()[E:\dev\beta1\frameworks\projects\rpc\src\mx\messaging\MessageResponder.as:235]
Most likely, this error is due to the wrong type of the value returned by the PHP count() method. You’ll get this error if you write your code like this:
1: $sql = 'SELECT COUNT(emp_no) AS empCount FROM employees';
2: $result = mysql_query($sql, $this->_con);
3: $row = mysql_fetch_object($result);
4: $ret = $row->empCount;
5: mysql_free_result($result);
6: return $ret;
In this case $ret variable will be a string, and when the values is received by the Flex code it is a int or number, and thus the error. So, just make sure you cast the $ret variable to int. You can do something like this:
1: $ret = $row->empCount + 0;
Conclusion
You can download the project from here (in the project you’ll find in the project a folder called services; copy the two PHP files to your webserver, and use the db.sql to create the table; next make sure you set the correct credentials for database access in the Employees.php file). Finally, you might want to generate again the whole data service, in order to have the gateway.php file generated (this file is created in the Flex output folder of the project, and this folder doesn’t get exported so you won’t find it in my project).
I’m a big fan of IDE features that can save you from tedious tasks or eliminate repetitive tasks. And this feature I think saves you time. You can check for more articles on Adobe Developer Connection, or even better, download Flash Builder 4 and play with it. Be sure to let me know what you think.
Round-trip Flash Catalyst and Flash Builder 4?
One question I heard a lot in the last weeks was about the round-trip between Flash Catalyst and Flash Builder 4. Out of the box, there is no round-trip between these two products. We do, however, support round trip between Flash Catalyst and Illustrator/Photoshop, and between Flash Builder 4 and Flash Professional CS 4.
Let’s take this workflow:
- The designer creates the user interface in Illustrator
- Using Flash Catalyst he transforms the static design into a functional design
- He hands the FXP project created by the Flash Catalyst tool to the developer
- The developer imports the FXP project into Flash Builder 4 and starts to add client logic and data services
- The designer has to change something, and wants to work directly on the developer’s project.
This is the message you get when you try step 5:
What are the challenges for a round-trip between Fc and Fb?
Why does this limitation exist in the first place? Before answering this question, let me tell you that we definitely want to have a round-trip between these two products, and we might have it in the future (keep in mind that Flash Catalyst is a version 1 product). Now, if you have a look at how a developer can work in Flash Builder 4 you can see two main approaches: Design view, or Code view. Sometimes you create custom components, and you do a lot of tricks and magic in order to create the desired application, and when you switch to Design view, nothing appears on the screen. This is really no big deal, because you can continue work in Code view, and press the Run button to check the application.
However, if you are the designer, and you work in Flash Catalyst, then all you have is the design view. If the design view can not render the application user interface and interactions on the stage, then the designer can’t work at all. Imagine trying to work on an Illustrator file without being able to visualize the document. It is really the same situation.
The solution
After giving this matter some thought, I think there is some kind of a round-trip that can be employed between these two products.
There are couple of things you need to do in order to succeed:
- The designer always keeps an instance of the FXP project that will not be touched by the developer. For example when he gives the FXP to the developer, he makes sure that he also keeps a copy of that file.
- The developer does his magic on the file received from the designer.
Now, suppose the designer has to modify the UI of the application to accommodate client’s wishes. He opens his FXP project, and makes all the changes. When he finishes, he gives a copy of the project to the developer. It doesn’t matter what kind of changes the designer made; if you look at the project you might find: new files, files deleted, existing files changed.
Which means that the easiest way to merge the designer changes into the developer project is by using a Merge tool or File/Folder Comparison tool such as Araxis. And just to illustrate my point, I added a new button in Flash Catalyst, then I saved the project. After this I unzipped the new project in a folder called v2 and the first version (or the developer’s project) in a folder called v1. Then, using Araxis I compare the two folders:
I can see very quickly if there are new files (like Button4.mxml), or if there are changes in the existing files (like Main.mxml). In the next image you can see the differences highlighted for Main.mxml.
Finally, there is a way to see the generated code in Flash Catalyst it self, and even to change it. Flash Catalyst offers a Code view (on the top-right corner you can choose between Code and Design view):
If you go in Code view, actually you can see the project as it will appear in Flash Builder when imported. While being in Code view, if you open an MXML file, you can see the code, but you cannot change it. And this is perfect, because, as I said, this is a designer tool and designers usually don’t work with code, but with tools. If you really want to play with the code inside of Flash Catalyst (maybe you are a designer/developer), you right-click on a file and choose to open it with Text Editor, then you can actually change it inside of the tool. But keep in mind, you might ruin the Design view by doing this.
Conclusion
I am not saying this is easy to do. But I really think it can speed up the integration of the designer changes into the developer project. And because we are still working on version one of the Flash Catalyst, we are still thinking and figuring out things.
Please, let me know what your opinion is on this matter.
Working in Flash Builder 4 with Flex and PHP
On June 1st we launched the public beta for Flash Builder 4, the newest iteration of our Eclipse-based IDE for RIAs, formerly known as Flex Builder 3. While you can find both evolutionary and revolutionary features in it, in this article I will touch on the data features and Flex – PHP integration features of Flash Builder 4.
In this article, I will show you how can you use a PHP class that manages a MySQL table (offering CRUD operations) in a Flex project. I will build the Flex project and I rely on Flash Builder 4 to set up the Zend Framework for me(because I want to use remoting as a way to talk with the PHP server). After this I will use the new wizard from Flash Builder 4 that introspects a PHP class and creates the ActionScript code to consume that PHP class.
In the end I should be able to have a simple Flex app that lets me see the data records, edit them, and add a new item. For some of the features I’ll have to write code, for others Flash Builder will generate the code. All in all, I think the application can be created in less than 30 minutes.
PHP code
I have two PHP classes that I want to reuse with my Flex application. One class is a data object that acts asa wrapper for one row from my database (MySQL). Here is the structure of the table:
authors
——————————
id_aut – primary key
fname_aut string
lname_aut string
The PHP data object, called VOAuthor, looks like this:
1: class VOAuthor {
2:
3: public $id_aut;
4: public $fname_aut;
5: public $lname_aut;
6: }
The second class, called Authors, is the class that manages the table, offering CRUD operations. The code is very straight forward, I don’t use any database abstraction layer. I have three methods one each for reading, deleting, and updating/inserting a record. I use the data object, VOAuthor, within these methods. This is the code:
1: require_once 'VOAuthor.php';
2:
3:
4: //connection 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 Authors {
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('error', 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: //logMe($author);
45: //connect to the database.
46: $mysql = mysql_connect(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD);
47: mysql_select_db(DATABASE_NAME);
48: if ($author->id_aut > 0) {
49: //save changes
50: $query = "UPDATE authors_aut SET fname_aut='".$author->fname_aut."', lname_aut='".$author->lname_aut."' WHERE id_aut=". $author->id_aut;
51: } else {
52: //add new record
53: $query = "INSERT INTO authors_aut (fname_aut, lname_aut) VALUES ('".$author->fname_aut."', '".$author->lname_aut."')";
54: }
55: $result = mysql_query($query);
56: return NULL;
57: }
58:
59: public function deleteData($author) {
60: if ($author == NULL)
61: return NULL;
62:
63: //connect to the database.
64: $mysql = mysql_connect(DATABASE_SERVER, DATABASE_USERNAME, DATABASE_PASSWORD);
65: mysql_select_db(DATABASE_NAME);
66: //add new record
67: $query = "DELETE FROM authors_aut WHERE id_aut = ".$author->id_aut;
68: $result = mysql_query($query);
69: return NULL;
70: }
71:
72: }
These two scripts sit inside of a folder called “remoting” in the root of the Apache web server document root folder.
Creating the Flex project and the UI of the app
I have the PHP files in place; now it’s time to create the Flex application. First, I need to create a new Flex project. Having opened the Flash Builder 4, I use the “New Flex Project” wizard to create a new Flex project called FlexPHPAMF. Make sure that in the wizard you choose PHP as the server technology, and you give the correct information for the document root of the web server (absolute path and URL).
Now let’s create the UI of the application. To do this, I chose to go in design mode where I added two buttons and a data grid to the stage. One button is labeled “Get Data” and the other one “Save Data”.
Using the data service panel
Finally, I am ready to use the new data wizards.At the bottom of Flash Builder, there is a view called “Data/Services”. Click on “Connect to Data/Service” link. A wizard starts, and you can select the type of the data service.
Because I want to use the PHP classes I have, I choose PHP and I click “Next”.
On this page, you can select to either reuse an existing service from the server, or to let the wizard create a stub for you. Because I have the service, I want to use the Import PHP class option. Flash Builder introspects the service in order to detect the supported operations (in this case, because I am using remoting, it detects the public methods and the return type). But to do so, it needs to install the Zend Framework to your web server.
What is very cool is that you don’t have to worry about the steps needed to install Zend Framework. Flash Builder will do it for you automatically.
After the Zend Framework is installed, on the last page of the wizard you’ll see the available methods. Click Finish.
Now, in the Data/Services view you should have a tree, with the root node named Authors. This is the name of the PHP class that you want to use in Flex. Under this node you have Data Types, and the three methods: deleteData(), getData(), saveData().
At the same time if you take a look at the project structure in the Package explorer, you’ll notice that new packages and files were created.
There is a new package called services.authors, and here you’ll find the service class that you’ll use to connect to the PHP class (Authors.as). This class actually extends a base class that implements all the logic using RemoteObject. The reason for this approach is that when you need to do your customization, you’ll do it in the Authors class. If you decide to use Flash Builder to regenerate the code for the same service (maybe as a result of a change on the server side), this operation will not over write your custom code.
The same goes for the data object (or value object). Flash Builder generated the ActionScript value object to match the PHP class VOAuthor. But again, you might want to do some customizations, and you can do it by touching VOAuthor that extends _Super_VOAuthor. All the generated code stays in the _Super_VOAuthor.
And finally, you’ll find a folder called services that gives you quick access to the Authors.php class.
It is time to fill the data grid with the data from the server. To do so, first you need to make sure that the return type for the getData() operation is an array of VOAuthor objects. If you right click on the name of the operation in the Data/Services view and choose Configure Return Type, you have a way to specify the array of VOAuthor:
To bind the data grid to the result of the getData() operation you have to go into design mode, and then drag and drop the getData() (from the Data/Services view) to the data grid.
If you switch to code view, you’ll see that some ActionScript code was added to the flexPHPAMF.mxml. What you want to do is to grab the line from the function and create a new function that will be called when you click the “Get Data” button and paste the code inside of it. My code looks like this:
1: protected function getData():void
2: {
3: getDataResult.token = authors.getData();
4: }
5: [...]
6:
7: <s:Button x="43" y="220" label="Get Data" click="getData()"/>
The wizard altered the data grid itself, you have the name of the table fields as labels, and the data provider of the data grid is set to the lastResult of the getDataResult object (this object was added to your code by the wizard, in order to help you manage the results of the authors service).
If you run the application and press the “Get Data” button, you should see the data.
Adding editing capabilities
The final part of this tutorial is adding the editing capabilities. I want to be able to edit an existing record using the data grid itself, and I want to have a form and a new button. First go back into design mode, drag and drop an new button on the stage, and change the label to “New”. Then, you’ll use another wizard to generate the form for editing. To do so, you select from the Data/Services view the Data Types node, expand it, and select the VOAuthor node. Right click it and select Generate Form. In the wizard choose “Make form editable” and click Next.
In the second page of the wizard deselect the “id_aut” property. This value will be auto-inserted by the database itself. Click Finish.
After the form is generated, you want to grab the form and position it maybe below the buttons. Also, you may want to change the labels. Finally add a new button in the form and label it “Add”.
Switch back to code view. Now, you need to make some changes. First of all (I think it is a bug), the text fields are not editable (they are Text instead of TextInput). So change the type to TextInput to make them editable. Next, give an ID to the Form itself. I want to have the form invisible when the application is loaded, and only make it visible when the users clicks “New”. For that you set the visible property on the form to false, and you add some code to the New button. Here is my code:
1: <s:Button x="224" y="220" label="New" click="myForm.visible = true"/>
2: <mx:Form x="46" y="277" id="myForm" visible="false">
3: <mx:FormItem label="First Name:">
4: <mx:TextInput id="fname" text=""/>
5: </mx:FormItem>
6: <mx:FormItem label="Last Name:">
7: <mx:TextInput id="lname" text=""/>
8: </mx:FormItem>
9: <mx:FormItem>
10: <s:Button label="Add"/>
11: </mx:FormItem>
12: </mx:Form>
And if you look in the code, you’ll notice a new object was created:
1: <authors:VOAuthor id="vOAuthor"/>
You’ll use this instance of the value object VOAuthor to send the new item to the server (remember, the PHP saveData() method expects a single argument of type VOAuthor).
To do so, you need to create a binding between the two text fields from the form and the properties fname_aut and lname_aut of the VOAuthor.
1: <fx:Binding destination="vOAuthor.fname_aut" source="fname.text"/>
2: <fx:Binding destination="vOAuthor.lname_aut" source="lname.text"/>
The last missing piece is the actual call to the saveData() method. This is very simple;on the click event of the “Save” button you add the call to the authors object getData() method, passing the local instance of VOAuthor (vOAuthor) as the argument:
1: <s:Button label="Add" click="authors.saveData(vOAuthor)"/>
You can add a new record now. What about editing an existing one? For this we will need a little bit more code. First, you want to register a listener on the data grid in order to know when something was edited (if you set the property editable to true on the data grid, then you can edit information inline). The event you are looking for is itemEditEnd:
1: <mx:DataGrid x="44" y="37" id="dataGrid" dataProvider="{getDataResult.lastResult}"
2: itemEditEnd="dataEdited(event)" editable="true">
Now,let’s define the listener, dataEdited(). Add the following code in the script section of the page:
1: private function dataEdited(event: DataGridEvent):void
2: {
3: vOAuthor = event.itemRenderer.data as VOAuthor;
4: }
This code uses the instance of the value object VOAuthor to store the data used by the edited row.
The last two things you have to do is to create a function called saveData() that is called when the user clicks the “Save Data” button, and to add initialization code for the vOAuthor object when the users clicks the “New” button (this way you avoid updating an item instead of adding a new one if you first edit a row and then add one):
1: private function saveData():void {
2: if (vOAuthor != null)
3: authors.saveData(vOAuthor);
4: }
1: <s:Button x="224" y="220" label="New"
2: click="myForm.visible = true; vOAuthor = new VOAuthor()"/>
The final code of the MXML file should look like this:
1: <?xml version="1.0" encoding="utf-8"?>
2: <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/halo" minWidth="1024" minHeight="768" xmlns:authors="services.authors.*">
3: <fx:Script>
4: <![CDATA[
5: import mx.events.DataGridEvent;
6: import services.authors.VOAuthor;
7: import mx.events.FlexEvent;
8: import mx.controls.Alert;
9:
10: protected function getData():void
11: {
12: getDataResult.token = authors.getData();
13: }
14:
15: private function dataEdited(event: DataGridEvent):void
16: {
17: vOAuthor = event.itemRenderer.data as VOAuthor;
18: }
19:
20: private function saveData():void {
21: if (vOAuthor != null)
22: authors.saveData(vOAuthor);
23: }
24:
25: ]]>
26: </fx:Script>
27: <fx:Binding destination="vOAuthor.fname_aut" source="fname.text"/>
28: <fx:Binding destination="vOAuthor.lname_aut" source="lname.text"/>
29: <fx:Declarations>
30: <s:CallResponder id="getDataResult"/>
31: <authors:Authors id="authors" destination="Authors" endpoint="http://localhost/flexPHPAMF-debug/gateway.php"
32: fault="Alert.show(event.fault.faultString)"
33: showBusyCursor="true" source="Authors"/>
34: <authors:VOAuthor id="vOAuthor"/>
35: </fx:Declarations>
36: <mx:DataGrid x="44" y="37" id="dataGrid" dataProvider="{getDataResult.lastResult}"
37: itemEditEnd="dataEdited(event)" editable="true">
38: <mx:columns>
39: <mx:DataGridColumn headerText="id_aut" dataField="id_aut"/>
40: <mx:DataGridColumn headerText="fname_aut" dataField="fname_aut"/>
41: <mx:DataGridColumn headerText="lname_aut" dataField="lname_aut"/>
42: </mx:columns>
43: </mx:DataGrid>
44: <s:Button x="43" y="220" label="Get Data" click="getData()"/>
45: <s:Button x="129" y="220" label="Save Data" click="saveData()"/>
46: <s:Button x="224" y="220" label="New" click="myForm.visible = true; vOAuthor = new VOAuthor()"/>
47:
48: <mx:Form x="46" y="277" id="myForm" visible="false">
49: <mx:FormItem label="First Name:">
50: <mx:TextInput id="fname" text=""/>
51: </mx:FormItem>
52: <mx:FormItem label="Last Name:">
53: <mx:TextInput id="lname" text=""/>
54: </mx:FormItem>
55: <mx:FormItem>
56: <s:Button label="Add" click="authors.saveData(vOAuthor)"/>
57: </mx:FormItem>
58: </mx:Form>
59:
60: </s:Application>
The delete operation is left as homework for you! Basically you want to get the selected row, grab the object data used by that row and save it into vOAuthor, and then call the authors.deleteData(vOAuthor).
Conclusions
If you are not sure what remoting and the Zend Framework are, you can read my post that explains these matters: Flex and PHP: remoting with Zend AMF. This article uses Flex Builder 3 (the previous version of Flash Builder 4).
You might be wondering about many-to-many or one-to-many associations. Well, at least for now there is no support for PHP (at least there is no wizard that handles any association and generates all the code). Still, I think I could do it much easier using these wizards than without them. Having the glue code generated for me and the Value Object created automatically, being able to have a form generated for a particular data model are all great help. You save time, you save money. Remember with any line of code you write, you might introduce a bug or more. Having more lines generated for you means less bugs
.
If you want to take a look at my project, you can download it from here (you’ll find the PHP files and the dump of the database in the service folder from this archive). If you want to install Flash Builder 4, you can grab the beta version from here and use it.
Finally I encourage you to try this for yourself, and let me know what you think. If you find bugs or you want more features in Flash Builder 4, be sure you go to http://bugs.adobe.com and ask your friends to vote for the bug. In the end, this is a beta product. If we can improve it, why not?
