One of the most useful concepts in software development is the practice of creating modular, reusable code. As a developer, you’re likely familiar with the heartache of reinventing the wheel. Doing so is certainly sometimes necessary, but when it isn’t, you would be well served by using plug-and-play components.
One problem that often arises when using your own modular components is that they haven’t been thoroughly tested, and the responsibility for their function rests solely on you—the creator. So, if there is a bug, you end up backtracking through every project that uses that code. As a result, many developers turn to third-party modules supported by an active community or corporate ownership. Each has distinct advantages and disadvantages: An active community drives rapid development and copious amounts of user-generated documentation, but the ultimate responsibility for the quality of the software may end up on you—the component’s user. Commercial code has one ultimately accountable entity but can be expensive and restrictively licensed. However, as an Adobe Flex and PHP developer, you don’t have to choose between flexibility and dependability.
Zend Technologies, PHP’s corporate sponsor, is also the sponsor of the open source Zend Framework, a collection of free, reusable PHP modules. And, as of version 1.7, Zend Framework includes an extension for native Adobe Action Message Format (AMF) communication between Adobe Flash Platform applications and PHP. The Zend Framework has not only a committed corporate sponsor but also an active developer community, and it is widely used in production environments. This doesn’t mean that there are no bugs, but you can be confident that when problems do arise, the community and Zend are dedicated to addressing them.
The Zend Framework includes extensions for a vast array of common programming tasks, including database connectivity and abstraction, e-mail, authentication, and third-party application programming interface (API) access. Most of these are useful in Flex applications, and with Zend_Amf, leveraging them has never been easier. In this article, you create a Flex application that takes advantage of some useful Zend Framework extensions. Using Zend_Amf, the Community Volunteer Flex application will:
- use Zend_Db to store persistent data in a MySQL database.
- validate respondents using a Zend_Captcha image.
- notify the volunteer coordinator using Zend_Mail.
To create this application, you need the following tools:
- Adobe Flex Builder version 3.0.2 or later or Zend Studio for Eclipse version 6.1.1 or later with the Flex Builder version 3.0.2 or later Eclipse plug-in, which is the recommended option (See the article, “Integrated PHP and Flex development with Zend Studio and Flex Builder,” for setup instructions.)
- A web server with PHP version 5 and MySQL version 5 installed
- The GD PHP extension to generate images
- Zend Framework version 1.7.3 or later
Note: The application and code presented here are for educational and testing purposes and should not be used unmodified in a production environment.
The Community Volunteer Application
The Community Volunteer application is fairly straightforward, and creating it serves as a good introduction to some of the most popular extensions of the Zend Framework. You’ll be using Flex Builder to create a Flash Platform client, and the Zend Framework helps form the PHP back-end services. Figure 1 shows the basic application flow, assuming success at each juncture.
Figure 1. Basic application flow
As you can see, the client application and PHP services do all the heavy lifting, and the user has only to complete the form and submit it. The whole volunteer sign-up process should take between one and two minutes, with no more than 3–5 seconds of “processing time.” The rich client you create in Flex helps to ensure that any data-entry errors are caught and corrected before the server-side code swings into action.
The Community Volunteer application uses a custom object, the Volunteer. You can use custom objects across Flex and PHP to ensure data integrity and business rules. The Volunteer object has the following attributes:
- id. An auto-incremented integer
- f_name. The volunteer’s first name
- l_name. The volunteer’s last name
- email. The volunteer’s e-mail address
- phone. The volunteer’s phone number
- hpw. The volunteer’s desired number of hours of service per week
- avail_days. The days the volunteer is available
- special_skills. Any skills the volunteer may have that would benefit the organization
These attributes will be reflected in the MySQL database. In this article, the database is called vols, and the table of volunteers is called volunteers. This Structured Query Language (SQL) script sets up a table of sample values in your database:
CREATE TABLE `volunteers` (
`id` int(5) NOT NULL auto_increment,
`f_name` varchar(55) NOT NULL,
`l_name` varchar(55) NOT NULL,
`email` varchar(75) NOT NULL,
`phone` varchar(15) NOT NULL,
`hpw` int(3) NOT NULL,
`avail_days` varchar(135) NOT NULL,
`special_skills` text,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
INSERT INTO `volunteers` (`id`, `f_name`, `l_name`, `email`,
`phone`, `hpw`, `avail_days`, `special_skills`)
VALUES(1, 'Richard', 'Bates', 'richard@flexandair.com',
'706-555-7100', 8, 'Monday, Tuesday. Thursday',
'Absolutely none');
INSERT INTO `volunteers` (`id`, `f_name`, `l_name`, `email`,
`phone`, `hpw`, `avail_days`, `special_skills`)
VALUES(2, 'Vicky', 'Vol', 'vicky@ivolunteer.fake',
'212-555-1212', 6, 'Monday, Wednesday, Friday',
'Experienced typing teacher');
INSERT INTO `volunteers` (`id`, `f_name`, `l_name`, `email`,
`phone`, `hpw`, `avail_days`, `special_skills`)
VALUES(3, 'Jimmy', 'Offershelp', 'jimmy@email.bogus',
'338-555-1212', 10, 'Tuesday, Saturday',
'Certified welder');
After you’ve set up your database, you’re ready to start on the PHP services.
Creating the PHP Services
The first step in setting up the PHP needed for the Community Volunteer application is obtaining the Zend Framework. Navigate to the Zend Framework website, and click Download Now. The page that follows presents several options, but this article assumes that you’re using the full Zend Framework version 1.7.3. Figure 2 shows the appropriate download link.
Figure 2. Downloading the Zend Framework
You’ll need to sign into your Zend account or create one at no cost. When the download is complete, extract the downloaded archive to a convenient location. The result should look similar to Figure 3.
Figure 3. File structure of the installed Zend Framework
Next, navigate to the web root folder of your web server. For example, if you’re using WAMPServer on
Windows, the default web root is C:\wamp\www; for MAMP on the Mac, the default web root is
Applications/MAMP/htdocs. Within this folder, create a new directory named Vols. This folder will be
the base directory for your Community Volunteer application. If you’re using Windows, copy the folder
named library from the Zend Framework download (see Figure 3) into the directory containing your web
root folder. If you’re using WAMPServer, the default is C:\wamp. If you’re using MAMP on a Mac, the
MAMP folder already contains a library directory. In this case, copy the /Zend subfolder to the
MAMP/Library directory to enable your application to use components from the Zend Framework.
Set Up the Directory Structure
Finally, you need to set up the directory structure of the application. Within the Vols folder, create a new directory called include. Then, open the new include folder and create a folder called services. Figure 4 illustrates the proper resulting structure.
Figure 4. Proper directory structure for your application
Write the PHP Code
With the folders in place, its time to begin writing the PHP code. This article assumes that you’re
using Zend Studio for Eclipse version 6.1.1. First, click File > New Project. For the project name,
type services, then clear the Use Default check box. In the Directory box, type the path to your Web
Root/Vols/include/services folder. Figure 5 shows the correct settings on a Mac using MAMP web server.
Figure 5. Setting up your project
Click Finish; your new project appears in the left pane of Zend Studio. The first file you create defines the Volunteer object. To create this file, right-click the services folder, and then choose New > Folder. In the New Folder dialog box, name the folder VO. Next, right-click the VO folder, then choose New > PHP File. In the dialog box that appears, type the file name Volunteer.php, and then click Finish. The file opens in the editor pane of Zend Studio. The contents of the Volunteer.php file are sparse and simple:
<?php
class Volunteer {
public $id = null;
public $f_name = '';
public $l_name = '';
public $email = '';
public $phone = '';
public $hpw = '';
public $avail_days = '';
public $special_skills = '';
}
?>
The file simply declares the class and enumerates the variables you need to access from your services, which correspond directly to the column names in the MySQL database. Save and close the file, then create another new PHP file in the /services folder. Name this file DBConnection.php. You use this file to store the database connection details and make the connection available to other services. First, you need to make sure you have access to the required Zend Framework files. There are several ways to include the Zend Framework: This article uses a set_include_path statement later to load the needed files. This method is not the most convenient if you plan on permanently installing the Zend Framework for use in several projects, but it is useful for accessing the Framework components as needed. For built-in access to the Zend Framework in multiple projects, you need to add the /library folder to your PHP include path. For more information on this and other options, see the Zend Framework Documentation.
DBConnection.php uses Zend_Db. To include this extension, begin your file with an include statement:
<?php include 'Zend/Db.php';
Be sure to adjust this line to reflect your Zend Framework location. Next, declare your DBConnection class:
class DBConnection {
Then, you need to create a function to connect to a MySQL database and return that connection so it can be used elsewhere. Call that function getConnection():
function getConnection() {
To begin the function, you need to instantiate a new Zend_Db::factory object. Pass two arguments to the new factory: the database driver to be used and an array containing the connection details and credentials. This example uses the Pdo_Mysql driver and connects to the default installation of MySQL under MAMP on the Mac. Using Windows and WAMPServer, you’ll need to change the MySQL password to your value.
$db = Zend_Db::factory('Pdo_Mysql', array(
'host' => 'localhost',
'username' => 'root',
'password' => 'root',
'dbname' => 'vols'
));
Finally, this function needs to return the connection so that your other services can use it:
return $db;
}
}
?>
Be sure to add the closing brackets (}) and tag (?>) to the class, function, and <?php block.
Create the Service for the Flex Client
Now that you’ve defined the Volunteer object and your database connection, you’re ready to create a
service that the Flex client can use. Again, create a new PHP file in the services folder. Name this
one DBService.php. Begin the code by including the files you just created: Volunteer.php and
DBConnection.php. Then, declare the DBService class:
<?php
include 'DBConnection.php';
include 'VO/Volunteer.php';
class DBService {
Because these files are in the same directory as the DBService file, it’s both safe and logical to use the relative paths in the include statements.
It’s always a good idea to document your classes with PHPDoc blocks. Not only will they help you and other developers navigate your code, but they’ll also help to provide more debugging information for some errors. The first function in the DBService class will be for saving new volunteers to the database, so it needs to accept a Volunteer object. It will also return a string. To reflect this, add a PHPDoc block like the following:
/** * save a new volunteer * * @param Volunteer * @return string **/
Next, declare the saveVol() function, and assign a variable to the passed-in Volunteer object:
function saveVol(Volunteer $newVol) {
Then, get a connection to the database using the DBConnection class’s getConnection() method:
$db = DBConnection::getConnection();
You’ll need to populate a data object with the properties from the volunteer that the client passes in. This enables you to perform an insert using Zend_Db:
$data = array(
'f_name' => $newVol->f_name,
'l_name' => $newVol->l_name,
'email' => $newVol->email,
'phone' => $newVol->phone,
'hpw' => $newVol->hpw,
'avail_days' => $newVol->avail_days,
'special_skills' => $newVol->special_skills
);
$db->insert('vols', $data);
As you can see, the insert() function accepts the database table name as the first argument and the new record’s information as the second argument. Finally, return a string to let the client know the insertion was a success, then close out the code:
return "OK";
}
}
?>
Make the Service Available to Your Application
You now have all the basic parts of a Zend_Amf–compatible service. To make this service available to your applications, you’ll also need a gateway file. The gateway file instantiates the Zend_Amf server and passes it information about your services and custom objects. To create the gateway file in Zend Studio, click File > New > PHP File. For the file’s location, type the application’s base directory, Vols. This folder should be inside your web server’s web root. Then, name the file gateway.php and click Finish.
Begin the gateway file’s code with an include path declaration like this one:
<?php
if(set_include_path('/Applications/MAMP/Library/') === false){
die('Include path failed');
}
Again, be sure to change the path to match your setup, if necessary. Next, require_once the Zend_Amf server file and your DBService class:
require_once 'Zend/Amf/Server.php'; require_once 'include/services/DBService.php';
These next lines instantiate the server and tell it that the DBService class should be exposed. The latter is accomplished using the server’s setClass() method:
// Instantiate the server
$server = new Zend_Amf_Server();
$server->setClass('DBService');
Map Your Classes
Next, you map the Volunteer class to the class you’ll use in ActionScript on the Flex client. Call your class VolunteerVO, and set its mapping with the server’s setClassMap() method, passing in the ActionScript class name first and the PHP class name second:
$server->setClassMap('VolunteerVO', 'Volunteer');
The final lines of the gateway file instruct the server on what it should accept and what it should return:
$server->setProduction(false); $response = $server->handle(); echo $response; ?>
The setProduction() function is set to False, which causes the server’s output to be more verbose. This is useful during testing, but this function should never be set to False if the server is publicly available; the debugging information is more than you would want to share with the whole Internet. Next, assign the variable $response to the server’s handle() method to instruct the server to handle the request. Finally, the response is echoed back to the client. Be sure to add this part, because without it, your clients will not get a response.
Figure 6 shows how your files and folders should now be arranged.
Figure 6. The file structure as it currently stands
Test Your Work
To test your work so far, open a web browser and navigate to http://localhost/Vols/gateway.php. You should see something similar to Figure 7.
Figure 7. Testing your application
Your browser may prompt you to download a file instead of displaying “Zend Amf Endpoint,” and that is also a successful test. If neither of those events happens, review your code and check your PHP error log for problems. If your test was successful, you’re ready to add the Captcha service.
Generating Captcha Images for Use in a Flash Platform Client
If you’ve filled out online forms or commented on a blog, you’ve probably used Captcha before. It’s the little image with the garbled letters and numbers with a text box for you to type what you see. Captcha stands for “Completely Automated Public Turing test to tell Computers and Humans Apart.” This mechanism keeps spam bots from auto-filling forms with junk. You’ll be using Captcha to generate dynamic images with PHP, then display them in a Flex application.
To begin, create a new PHP file in your services folder in Zend Studio. Name the file CaptchaService.php. Inside the blank file, start by including two Zend Framework files:
<?php include 'Zend/Captcha/Image.php'; include 'Zend/Session/Namespace.php';
The Image.php file contains the code for generating Captcha images; the Namespace.php file enables you to keep track of which images were shown to a particular visitor. Next, declare the CaptchaService class and its first function, generate(). Then, instantiate a new Zend_Captcha_Image to the variable $captcha:
class CaptchaService {
/**
* generate a captcha image
*
* @return string
**/
function generate() {
$captcha = new Zend_Captcha_Image(); // new instance
Now, you need to pass some parameters to the $captcha object:
$captcha->setTimeout('300')
->setWordLen('6') // give us 6 letters
->setHeight('80')
->setFont('../../include/batm.ttf') // path to your font
->setImgDir('../../captcha'); // where the image is stored
You’ll need a TrueType font to generate the Captcha image. In this case, I’m using the batm.ttf font, which is in the Vols/include folder. You’ll also need a directory in which to store the images that Zend_Captcha generates. Create the captcha folder in the main Vols directory, and make sure it’s writable by your web server. The easiest way to do this is to grant Write access to Everyone on Windows and Mac. On Linux, set the captcha folder’s permissions to 777. Your directory and files should now be arranged like Figure 8.
Figure 8. The Captcha directory structure
Back in your CaptchaService.php file, complete the generate() function with the following lines:
$captcha->generate() //actually generate the image and session ID
return $captcha->getId();
} //end function generate
The getId() method returns the session ID, which is also the name of the generated image file. You’ll use this ID to both display the image and compare the user’s input to the correct answer with the validate() function:
/**
* validate a captcha
*
* @param array
* @return string
**/
function validate($info) {
$response['id'] = $info[0];
$response['input'] = $info[1];
This function begins by accepting an array, $info, which contains the session ID and the user’s answer to the Captcha challenge. These pieces of information are assigned to key–value pairs in the $response variable. Next, you need to get a new instance of Zend_Captcha_Image:
$captcha = new Zend_Captcha_Image(); // new instance
When the $captcha variable is initialized, its isValid() method is used to check the user’s input. You’ll need to pass in the $response variable containing the session ID and user input so that the Captcha Image class can determine the correct response:
if($captcha->isValid($response)) {
// All good.
return "OK";
}
else {
// Doesn’t match
return "NOMATCH";
}
Finally, close the function, class, and <?php tag:
}
}
?>
Now that your CaptchaService.php file is complete, you can add it to the services that Zend_Amf exposes. To do this, go back to your gateway.php file and add another require_once below the others—this time for CaptchaService.php:
require_once 'include/services/CaptchaService.php';
Then, add an additional call to $server->setClass below the others:
$server->setClass('CaptchaService');
The information you’ll be passing between the client and the CaptchaService class is pretty basic and doesn’t need a custom class or $server->setClassMap(), so the two lines above are all that is needed.
Sending E-mail Using Zend_Mail
The Zend_Mail extension makes sending e-mail messages through PHP incredibly easy. Only a few parameters are required, and most would be hard-coded in a typical PHP script. However, so this example can be extended, I walk through creating an Email class and sending an entire e-mail object to the class for processing. To begin, right-click the /services/VO folder in Zend Studio’s left pane once again, then choose New > PHP File. Name this file Email.php. Just as in your Volunteer.php file, you use this file to enumerate the properties of the object. The complete contents of the file should be:
<?php
class Email {
public $body = '';
public $from = '';
public $recipient = '';
public $subject = '';
}
?>
Save this file, then create another new file in the /services folder called EmailService.php. Before you declare the EmailService class, add your include statements for the custom Email object and the Zend_Mail extension:
include 'VO/Email.php'; include 'Zend/Mail.php';
Next, declare the class and add the PHPDoc block for the sole function you’ll be creating, sendEmail():
class EmailService {
/**
* send an email using sendmail
* @param Email
* @return String
**/
The sendEmail() function accepts an Email object as a parameter and returns a string to let the client know that it is complete. Here is the sendEmail function:
function sendEmail(Email $email) {
$mail = new Zend_Mail();
$mail->setBodyText($email->body);
$mail->setFrom($email->from);
$mail->addTo($email->recipient);
$mail->setSubject($email->subject);
$mail->send();
return "OK";
}
}
?>
The function begins by creating a new instance of Zend_Mail. Then, the mail object is populated with the values from the object that was passed in. Finally, the send() method is invoked, and the string OK is returned. To complete the file, add the closing tags and save it.
Once again, you’ll need to tell the Zend_Amf server to expose this class and how to map the Email object. Back in your gateway.php file, add another require_once statement:
require_once 'include/services/EmailService.php';
Then, add the necessary setClass() call:
$server->setClass('EmailService');
And finally, map the Email class in PHP to the EmailVO class in ActionScript:
$server->setClassMap('EmailVO', 'Email');
Save the file, and your PHP work is complete. You’re ready to move on to the Flex client.
Creating the Flash Platform Client with Flex Builder
If you’re using the recommended setup, you’ll have Flex Builder installed as a plug-in for Zend Studio for Eclipse. However, to begin working with Flex projects, you may have to switch perspectives in Zend Studio from PHP to Flex Development. To do this, click Window > Open Perspective > Other. Then, choose Flex Development from the dialog box.
There are two ways to create the Flex client in Zend Studio. One method is to add the Flex Nature to the project by right-clicking the main project folder and choosing Flex Project Nature > Add Flex Project Nature. The other method is to create a new Flex project by selecting File > New > Flex Project. If you are building a PHP/Flex project in Zend Studio, it may be more convenient to use the first method. However, if you use a different integrated development environment (IDE) for the PHP portion of your project, you’ll have to create a new project in Flex Builder. The steps for both methods are almost identical, but this article creates a new Flex project, because it works regardless of the PHP IDE used.
To create a new Flex project, click File > New > Flex Project. In the dialog box that appears, name your project Vols, then change the Application server type to PHP and click Next. In the next dialog box, enter the appropriate details for your server. You can use any directory within your web root. Figure 9 shows appropriate settings for MAMP on Mac.
Figure 9. Server details for MAMP on Mac
When you’re done, click Finish; the new project appears in the workspace. If you’re using all the defaults, your Navigator pane on the left should look like Figure 10.
Figure 10. Navigator hierarchy for your new project
The first thing you’ll want to do is create custom ActionScript class files that correspond to the Email.php and Volunteer.php classes. Start by expanding the Vols project folder; then, right-click the src subfolder and choose New > Folder. When you create custom classes in ActionScript, like many other languages, the protocol is to use dot-syntax to avoid confusion. It is also accepted practice to use domain names, as they are a handy way of ensuring uniqueness. Because I own the domain name flexandair.com, I’ll create the folder structure com/flexandair/ and place my custom classes in the flexandair folder. For convenience, you’ll want to do the same for this exercise. The classes can then be accessed in ActionScript by importing them from com.flexandair.MyClass. To set this up in your project, you can enter the full path in the New Folder dialog box: /com/flexandair. Click Finish, and both directories will be created.
Next, right-click the flexandair folder, then choose New > ActionScript Class. For the class name, type VolunteerVO. Click Finish, and the basic class file is generated. The first things you’ll need to add to this file are the [Bindable] tag and an alias definition for class mapping. Add these two lines right below the package declaration and opening bracket, on lines 3 and 4:
[Bindable] [RemoteClass(alias="VolunteerVO")]
The [Bindable] tag simply allows data binding to this class’s properties. The next line is to indicate that this class is mapped remotely as VolunteerVO. Next, you need to add the same properties here in ActionScript that you added before to the Volunteer.php file. Add these lines right below the class declaration and opening bracket, before the VolunteerVO constructor function:
public var id:int = 0; public var f_name:String = ''; public var l_name:String = ''; public var email:String = ''; public var phone:String = ''; public var hpw:Number = 0; public var avail_days:String = ''; public var special_skills:String = '';
Save the file, and create another ActionScript class called EmailVO, also in the flexandair folder. Repeat the steps above, this time adding this code to lines 3 and 4:
[Bindable] [RemoteClass(alias="EmailVO")]
Then, add the corresponding properties within the opening class declaration:
public var body:String = ''; public var from:String = ''; public var recipient:String = ''; public var subject:String = '';
Save the file, and your custom ActionScript classes are complete. Next, you need to tell your Flex application how to reach your Zend_Amf server. The services-config.xml file is an Extensible Markup Language (XML) document that specifies the properties of the remote services your application will use. To create this file, right-click the src folder of your Flex project, then choose New > File. Name the file services-config.xml, then click Finish. You’ll be presented with the new empty file. The default editor is the built-in XML editor, but you need to open it in the plain text editor by right-clicking the file and choosing Open with > Text Editor. Next, paste the following code into the file:
<?xml version="1.0" encoding="utf-8"?>
<services-config>
<services>
<service id="amf-remoting" class="flex.messaging.services.RemotingService" messageTypes="flex.messaging.messages.RemotingMessage">
<destination id="zend_amf">
<channels>
<channel ref="my-zend_amf"/>
</channels>
</destination>
</service>
</services>
<channels>
<channel-definition id="my-zend_amf" class="mx.messaging.channels.AMFChannel">
<endpoint uri="http://localhost/Vols/gateway.php" class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>
</channels>
</services-config>
The above XML block contains two key pieces of information specific to this application. The first is the id attribute of the <destination> node. You’ll use this ID to connect to the remote service in the Flex application. The other important attribute is the uri property of the <endpoint> node. Here, you need to place the path to your Zend_Amf gateway file on your web server. The class attributes correspond to the method of communication: you’ll be using “remoting” to communicate with your services in AMF.
Next, you need to tell the Flex compiler about your services-config.xml file. You do so using the compiler flag -services. To add this flag, click Project > Properties. A new window appears with a list on the left side. From that list, choose Flex Compiler. At the end of the existing text in the Additional compiler arguments box, add the following:
-services “services-config.xml”
The result should look like Figure 11.
Figure 11. Adding compiler arguments
Click Finish, and the workspace refreshes. When complete, you’ll have access to your Zend_Amf gateway from the client application.
Building the Client Application
To start building the client application, open the main application file, Vols.mxml. MXML is a markup language similar to XML or Hypertext Markup Language (HTML) used primarily for creating user interface (UI) controls in Flex.
Line 2 of the Vols.mxml file contains the opening <mx:Application> tag and sets some of the application’s properties. Change the layout property from layout="absolute" to layout="vertical", which arranges your components vertically—handy for applications in which specifying the coordinates of everything on the screen isn’t necessary. Next, move down below the <mx:Application> tag. Here, you’ll create remote objects. Remote objects are used to access services exposed by Zend_Amf or other AMF gateways. First, create the opening tag of the CaptchaService remote object:
<mx:RemoteObject id="captchaService" destination="zend_amf" source="CaptchaService">
This opening tag specifies several attributes:
- Id. This ID must be unique within the Flex application, and it allows you to reference this remote object elsewhere in the code.
- Note: I’m using the same names here as in the PHP file and class names and changing the first letter to lowercase. You can use any ID you like: Just bear in mind that you don’t have to match the MXML ID with the PHP class name. The source property must match the PHP class name (see below).
- Destination. This is the destination ID you specified in the services-config.xml file. Make sure this matches exactly.
- Source. This is the name of the PHP class you’re accessing through this remote object. This name must match the class name in your PHP code.
Specify Your Services
Next, you need to specify the services you want to access through the remote object—in other words, the methods you created in the CaptchaService class. Create the generate() method first:
<mx:method name="generate" result="onCaptchaGenerated()"fault="onFault(event)" />
The <mx:method> tag also contains some important attributes:
- Name. This is the name of the method in PHP. Be sure to set the name of the method exactly as it is specified in your CaptchaService.php file.
- Result. When the method returns a result, this ActionScript function (which you’ll create momentarily) will be called.
- Fault. In the event of a fault, the onFault() function is called. Notice that you are passing the event itself to the onFault() function. You’ll also create this function in a moment.
Notice that this opening <mx:method> tag is closed with the shorthand />. Remember, the <mx:RemoteObject> tag has not yet been closed, and this method is being placed within the remote object tags. Before you close the <mx:RemoteObject> tag, insert another <mx:method>, this time for the validate() method:
<mx:method name="validate" result="onValidateResult()" fault="onFault(event)" />
Now, add the closing </mx:RemoteObject> tag:
</mx:RemoteObject>
You’ll need to create remote objects and methods for the other services—EmailService and DBService—as well. Below is the code for these. Always make sure you specify a unique ID each time and that your method names and source attributes match the PHP function and class names, respectively:
<mx:RemoteObject id="dbService" destination="Zend_Amf" source="DBService">
<mx:method name="saveVol" result="onVolSaved()" fault="onFault(event)" />
</mx:RemoteObject>
<mx:RemoteObject id="emailService" destination="Zend_Amf" source="EmailService">
<mx:method name="sendEmail" result="onEmailSent()" fault="onFault(event)" />
</mx:RemoteObject>
Lay Out the Visual Components
Next, start laying out the visual components of the application by adding a label:
<mx:Label text="New Volunteer Signup Form" fontSize="14"/>
You’ll also need a form for the user to enter the required information. Forms are very easy to create using Flex Builder’s visual designer; the code that follows was created in Design mode. Below the code, I discuss the individual elements.
<mx:Form height="356" horizontalScrollPolicy="off" verticalScrollPolicy="off">
<mx:FormItem label="First Name">
<mx:TextInput width="181" id="fNameInput"/>
</mx:FormItem>
<mx:FormItem label="Last Name">
<mx:TextInput width="181" id="lNameInput"/>
</mx:FormItem>
<mx:FormItem label="Email">
<mx:TextInput width="181" id="emailInput"/>
</mx:FormItem>
<mx:FormItem label="Phone">
<mx:TextInput width="181" id="phoneInput"/>
</mx:FormItem>
<mx:FormItem label="Hours Per Week">
<mx:TextInput width="181" id="hpwInput"/>
</mx:FormItem>
<mx:FormItem horizontalAlign="left" label="Available Days" height="89" width="297" horizontalScrollPolicy="off">
<mx:Canvas width="184" height="83" id="daysForm">
<mx:CheckBox label="Monday" x="10"/>
<mx:CheckBox label="Tuesday" x="108"/>
<mx:CheckBox label="Wednesday" x="10" y="19"/>
<mx:CheckBox label="Thursday" x="108" y="19"/>
<mx:CheckBox label="Friday" x="10" y="40"/>
<mx:CheckBox label="Saturday" x="108" y="40"/>
<mx:CheckBox label="Sunday" x="10" y="61"/>
</mx:Canvas>
</mx:FormItem>
<mx:FormItem label="Special Skills?" height="93">
<mx:TextArea height="93" width="182" id="skillsInput"/>
</mx:FormItem>
</mx:Form>
Now, that’s a lot of code, but it’s pretty straightforward. In fact, it should look familiar if you work with HTML. Let’s examine one of the text inputs—a single-line entry box with a label:
<mx:FormItem label="First Name">
<mx:TextInput width="181" id="fNameInput"/>
</mx:FormItem>
As you can see, an <mx:FormItem> tag encapsulates each control on the form. Then, the label’s text property is set. Next, the input portion of the form item, in this case a TextInput, is placed within the <mx:FormItem> tag. The width is set, and a unique ID is specified. The ID allows you to access the component elsewhere in the code. Each TextInput in the form follows this example.
Next is a more complicated component: a set of check boxes that allow the user to select all the days of the week he or she is available. The check boxes are inside an <mx:Canvas> tag, which allows you to rearrange them to take up less space without the application forcing them into the default vertical layout:
<mx:FormItem horizontalAlign="left" label="Available Days" height="89" width="297" horizontalScrollPolicy="off">
<mx:Canvas width="184" height="83" id="daysForm">
<mx:CheckBox label="Monday" x="10"/>
<mx:CheckBox label="Tuesday" x="108"/>
<mx:CheckBox label="Wednesday" x="10" y="19"/>
<mx:CheckBox label="Thursday" x="108" y="19"/>
<mx:CheckBox label="Friday" x="10" y="40"/>
<mx:CheckBox label="Saturday" x="108" y="40"/>
<mx:CheckBox label="Sunday" x="10" y="61"/>
</mx:Canvas>
</mx:FormItem>
You’ll also notice that properties called horizontalScrollPolicy and verticalScrollPolicy are sometimes set to Off. This simply means that even though your components may be very close to the borders of their parent containers, you still don’t want scrollbars created.
Next, add controls for the Captcha image and the user’s response. Add these below the form’s closing tag:
<mx:Image id="captchaImage"/> <mx:Label text="Please enter the characters you see in the box below"/> <mx:TextInput id="captchaInput"/>
The image control is used, predictably, to display images. However, you would normally set a URL where the image is located using the source property. You don’t do that here, because the Captcha image is dynamically created for each session. Finally, add a Submit button so that all your grand designs can start swinging into motion:
<mx:Button label="Submit" click="validate()"/>
Write the ActionScript Functions
Now that all the required user controls are in place, you can proceed to writing the ActionScript functions. ActionScript works with MXML in Flex applications in a conceptually similar way to HTML with JavaScript code. In Flex, you need to place your ActionScript code inside an <mx:Script> block. Start typing this tag below the <mx:Button> tag for the Submit button. Use Flex Builder’s auto-complete function, and it should generate the basic ActionScript block, which begins like this:
<mx:Script>
<![CDATA[
The CDATA tag simply tells the Flex compiler that what follows is ActionScript, not MXML. Right below the CDATA tag, you need to add some import statements for the classes you’ll be using in the application:
import mx.controls.CheckBox; // checkbox control class import mx.rpc.events.FaultEvent; // fault events thrown by remote objects import mx.controls.Alert; // pop-up alerts import com.flexandair.VolunteerVO; // custom VolunteerVO object import com.flexandair.EmailVO;// custom EmailVO object
Now, you need to create two variables here before you write any functions. These variables will be accessible for all the functions that follow. You’ll also precede them with a [Bindable] tag to enable data binding:
[Bindable] private var sessionID:String = ''; private var vol:VolunteerVO = new VolunteerVO();
The sessionID string holds the string returned by the captchaService’s generate() method. It will be used to display the Captcha image and to verify the response. The vol:VolunteerVO object holds the visitor’s information from the form in its properties. When the user submits the form, this object will be passed to the Zend_Amf gateway.
The first function you’ll create is the onCaptchaGenerated() function. This function is set to be called on the captchaService.generate() method’s result event:
private function onCaptchaGenerated():void {
sessionID = captchaService.generate.lastResult;
captchaImage.source = "http://localhost/Vols/captcha/" + sessionID + ".png";
}
The first thing that takes place here is that the sessionID is set to a value returned by Zend_Amf from the generate() function in PHP. Next, that value is used to create the URL to the Captcha image. Make sure this path leads to the /captcha images folder on your web server. The extension .png is appended, because that is the format of the Captcha image; as of this writing, PNG is Zend_Captcha_Image’s only supported output format. This URL is assigned to the source property of the captchaImage control you created in MXML a moment ago. Now, the entry form will show the appropriate Captcha image to the user.
To check the user’s input, create a validate() function:
private function validate():void {
var info:Array = new Array();
info[0] = sessionID;
info[1] = captchaInput.text;
captchaService.validate(info);
}
In the validate() function, the first line creates a new array. The sessionID and the user’s Captcha response are then placed in the array. Because the Captcha response text input has an ID of captchaInput, you access the contents of the text input using captchaInput.text. Finally, the array is sent to the Zend_Amf gateway, ultimately destined for the validate() method in CaptchaService.php. All of your Zend_Amf remote methods will be accessed this way, using the format RemoteObjectID.remoteMethodName(infoToPassIn).
The next step is to bind the user’s input to a new VolunteerVO object so that his or her details can be sent to PHP and saved in the database. To keep it simple, you’ll be chaining all the ActionScript functions together, with each function predicated on the success of the previous one. To do this, use the validate() remote method’s result event to trigger the next step, sending a new volunteer to the database. If the Captcha validation comes back as OK, the new VolunteerVO will go to the DBService class in PHP:
private function onValidateResult():void {
if (captchaService.validate.lastResult == "OK") {
//Proceed
vol.f_name = fNameInput.text;
vol.l_name = lNameInput.text;
vol.email = emailInput.text;
vol.phone = phoneInput.text;
vol.hpw = Number(hpwInput.text);
vol.avail_days = listDays();
vol.special_skills = skillsInput.text;
dbService.saveVol(vol);
}
else {
//incorrect
Alert.show("No Good","BAD");
captchaService.generate();
captchaInput.text= '';
}
}
The if statement in ActionScript is similar to the one in PHP. If the result is OK, the values from the form are collected and placed in a new VolunteerVO object. The hpw variable is a number, but <mx:TextInput> controls store everything in the text property as a string. Therefore, you need to use the Number(string) function to convert the type. Finally, the dbService.saveVol() remote method is called, with the new VolunteerVO being passed in.
If the user’s response to the Captcha is incorrect, a new image is produced and the text box cleared, allowing the user to try again. Again, the remote method for generating a new Captcha image is accessed using the format RemoteObjectID.remoteMethodName(). Recall that the onCaptchaGenerated() function is triggered after the new image and session are created, therefore updating the image URL and the sessionID.
Also notice that the avail_days property is set by another function, listDays(). The listDays() function goes through all the check boxes, assembling a list of the selected days. By list, I mean a human list, not a programming data type. The day names are placed into a single string, separated by commas and spaces. Let’s go through the listDays() function:
private function listDays():String {
var days:String = ""; // holds the day names
var i:int = 0; // lets us know if this is the first day selected
for each (var cb:CheckBox in daysForm.getChildren()) {// daysForm contains all the day checkboxes
if (cb.selected == true) { // checkbox is checked
if (i == 0) { // item is first checked item
days += cb.label; // just add the name only, no commas or spaces
}
else { // not the first item
days += ", " + cb.label; // prefix with comma and space to build list
}
i++; // increment placeholder
}
}
return days; // return the list of selected days
}
The function begins by creating a local variable, days, in which to store the string containing the selected days. Next, a placeholder variable, i, is declared. Keeping track of the iteration number in the loop enables you to properly place the commas and spaces. Then, the loop begins. Because all of the check boxes are in an <mx:Canvas> tag with the ID daysForm, you can use daysForm.getChildren() to get an array containing all of them. For each check box that is a child of daysForm, the loop tests whether the box is selected. If it is, the loop tests i. If i=0, you know that this box is the first selected check box. Because you wouldn’t begin a list with a comma and a space, you simply add the check box’s label to the days string. If, however, i > 0, the selected day is not the first and should be preceded by a comma and space and appended to the list. At the end of the loop, the placeholder is incremented, and when all the check boxes have been tested, the list string is returned. The days string looks something like Monday, Wednesday, Sunday (depending on the days selected).
Working with the Results
With all the necessary information now being assigned to the new VolunteerVO and sent to the DBService in PHP, you’re ready to handle the result of the saveVol() method.
private function onVolSaved():void {
Alert.show("Volunteer saved","OK");
sendEmail();
}
The first thing that happens here is that the user is shown an alert. The first string, Volunteer saved, is shown as the alert message, and OK is the alert’s title. Continuing your chain of functions, the sendEmail() function is called. You’ll use this function in almost the same way as the onValidateResult() function that assembled a new VolunteerVO object. This time, you create a new EmailVO object:
private function sendEmail():void {
var email:EmailVO = new EmailVO();
email.from = "noreply@flexandair.com";
email.subject = "New Volunteer Signup";
email.recipient = "richard@fakemail.com";
email.body = vol.f_name + ' ' + vol.l_name + " has just signed up as a new volunteer! " + vol.f_name + " is available " + vol.hpw + " hours per week on " + vol.avail_days + ".";
emailService.sendEmail(email);
}
This function is simple—essentially just setting the e-mail properties and assembling a message string for the body of the e-mail message. Obviously, you’ll want to change the e-mail address to one you can use for testing. ActionScript uses plus signs (+) to concatenate strings, and the dynamic information is populated from the VolunteerVO object. The new EmailVO is then sent to the remote object emailService (the MXML ID corresponding to the EmailService class in PHP), destined for the sendEmail() method. When a result comes back from this call, you’ll handle it with the onEmailSent() function:
private function onEmailSent():void {
Alert.show("email sent","OK");
}
This function displays an alert box to let you know the response was echoed back after the remote sendEmail() method finished.
One more function referenced earlier remains to be written: the onFault() function. If there is a problem communicating with Zend_Amf, you need to know about it. You’ll use an alert again:
private function onFault(event:FaultEvent):void {
Alert.show(event.fault.faultDetail + " Fault Code: " + event.fault.faultCode, event.fault.faultString);
}
The FaultEvent is passed in as the variable event. The event has a property called fault, which contains several pieces of information to help you figure out what went wrong: faultDetail, faultCode, and faultString, among others. These are displayed in the alert box.
Finally, you need to call the captchaService’s generate() method when the application first loads so that the image appears. To do this, go to line 2, inside the <mx:Application> tag. Add this to the properties inside the tag, after layout="vertical":
applicationComplete="captchaService.generate()"
Now, the captchaService.generate() remote method will be called when the application loads. Click the green Run button, and the application launches in your web browser. You should see something similar to Figure 12.
Figure 12. The finished form
Complete the form, then click Submit. You should get two alerts in quick succession. Figure 13 shows this result.
Figure 13. Submission result
Also, check the e-mail at the address you specified in the sendEmail() function. Figure 14 shows the notification e-mail message.
Figure 14. The notification e-mail message
Troubleshooting
If you get compiler errors in Flex Builder, look in the bottom pane on the Problems tab. Any issues with the MXML or ActionScript will be displayed here with a line number and a short description.
You might get a “BadVersion” or “DeliveryInDoubt” message if there is an issue with your PHP calls. Double-check the PHP code for common errors like closing tags and line endings, and make sure all your classes match their file names. Also ensure that each of your services are going into $server->setClass() in your gateway.php file.
If your Captcha image is not showing up in the Flex client, check the /captcha folder you created to hold the generated images. If the folder is empty, it may be that the permissions are blocking your web server from saving the images there. Make sure it is writable. Another possibility is that the CaptchaService class can’t find your font. Make sure you place a TrueType (.ttf) font in the folder you specified in CaptchaService.php. I used the /Vols/include folder in the example.
If the captcha folder contains images, make sure the onCaptchaGenerated() function in ActionScript is pointing to the right URL. If you followed this article verbatim, the URL is:
"http://localhost/Vols/captcha/" + sessionID + ".png";
Next Steps
When you have your gateway.php file set up properly, you can take advantage of most Zend Framework extensions or any PHP code, for that matter. For a full list of Zend Framework components, go to the Zend Framework Documentation site.
Another great resource for Flex and PHP development is the Adobe Developer Connection’s Flex and PHP site. There, you’ll find lots of information on developing with Flex and PHP—from streamlining your workflow to useful tips and real-world examples.


Comments (Login to leave comments)
Hey, I'm on vista using wamp and I got this error message when I ran the app:
Fault Code: Client.Error.DeliveryInDoubt
And I got these errors in my php error log:
[28-Feb-2009 02:30:42] PHP Warning: imageftbbox() [<a href='function.imageftbbox'>function.imageftbbox</a>]: Invalid font filename in C:\Web_Templates_Scripts\Zend\ZendFramework-1.7.5\library\Zend\Captcha\Image.php on line 483
[28-Feb-2009 02:30:42] PHP Warning: imagefttext() [<a href='function.imagefttext'>function.imagefttext</a>]: Invalid font filename in C:\Web_Templates_Scripts\Zend\ZendFramework-1.7.5\library\Zend\Captcha\Image.php on line 486
[28-Feb-2009 02:30:42] PHP Warning: imagepng() [<a href='function.imagepng'>function.imagepng</a>]: Unable to open '../../captcha/a612c322bdf45df4dd7901d7aac2c249.png' for writing: No such file or directory in C:\Web_Templates_Scripts\Zend\ZendFramework-1.7.5\library\Zend\Captcha\Image.php on line 557
How can I make my directory writable, and what do the errors about the font mean?
[28-Feb-2009 02:30:42] PHP Warning: imagefttext() [<a href='function.imagefttext'>function.imagefttext</a>]: Invalid font filename in C:\Web_Templates_Scripts\Zend\ZendFramework-1.7.5\library\Zend\Captcha\Image.php on line 486
Make sure the TrueType font you reference in the CaptchaService.php file is in the right place. It might help to use an absolute path. Check this line in CaptchaService.php and make sure your font file is there and spelled correctly:
->setFont('../../include/batm.ttf') // path to your font
The "channel disconnected" error, in my experience, is almost always because of an error in my PHP code.
If you need to make a folder writable, the easiest but least secure way is to right-click the folder, choose the Security tab, then edit the permissions. You may have to manually add the "Everyone" group to the list, but after you do, make sure "Everyone" has write-enabled. The safer way would be to set up a user for the Apache service to run as, then give that user write-permission on the necessary folder.
I changed the CaptchaService.php file, so that the directory the code is now:
->setFont('../../include/batm.ttf') // path to your font
->setImgDir('../../captcha'); // where the image is stored
I added a user called "Everyone" and gave it all the permissions for the directory captcha, but I still got the same errors as before.
The url that I am appears when I hit run on the mxml file is http://localhost/ZendProjects/Vols/bin-debug/Vols.html
My directory structure is:
C:\wamp\www\ZendProjects\Vols as the project root.
The mxml file is located in C:\wamp\www\ZendProjects\Vols\src
Here is what my directory looks like:
The actionscript code in the mxml file for the captcha source is:
captchaImage.source = "http://localhost/ZendProjects/Vols/captcha/" + sessionID + ".png";
My services-config.xml file endpoint uri is pointing to:
http://localhost/ZendProjects/Vols/gateway.php"
I am still getting the same errors in my php error log I mentioned in my previous post.
http://openfontlibrary.org/media/view/media/fonts
I would suggest using a TrueType font. Simply obtain the font and specify its path in place of the reference to "batm.ttf"
I got both the "BadVersion" and "DeliveryInDoubt" errors at first, but adding a full path to the captcha folder and font location resolved it for me. There was also an error when inserting data into the db. But changing the following line seemed to fix it for me.
$db->insert('volunteers', $data);
I've been using AMFPHP for most of my projects recently, but I'll likely starting using the Zend Framework, and Zend_Amf going forward. Thanks again!
[MessagingError message='Destination 'Zend_Amf' either does not exist or the destination has no channels defined (and the application does not define any default channels.)']
Couldn't establish a connection to 'Zend_Amf' Fault Code: InvokeFailed
I've added the compiler flag and my services-config.xml file is the src directory right next to my MXML file. Any ideas???
ARGH!
(other than that, great tutorial Richard. Love what you're doing with flashandphp.com as well)
<mx:RemoteObject id="captchaService" destination="Zend_Amf" source="CaptchaService" endpoint="http://localhost/testformflex/gateway.php">