Leveraging Zend Framework Components from Adobe Flash Platform Applications

February 25, 2009

Tutorials, Zend Framework

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:

  1. use Zend_Db to store persistent data in a MySQL database.
  2. validate respondents using a Zend_Captcha image.
  3. notify the volunteer coordinator using Zend_Mail.

To create this application, you need the following tools:

  1. 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.)
  2. A web server with PHP version 5 and MySQL version 5 installed
  3. The GD PHP extension to generate images
  4. 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:

  1. id. An auto-incremented integer
  2. f_name. The volunteer’s first name
  3. l_name. The volunteer’s last name
  4. email. The volunteer’s e-mail address
  5. phone. The volunteer’s phone number
  6. hpw. The volunteer’s desired number of hours of service per week
  7. avail_days. The days the volunteer is available
  8. 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:

  1. 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).
  2. Destination. This is the destination ID you specified in the services-config.xml file. Make sure this matches exactly.
  3. 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:

  1. 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.
  2. Result. When the method returns a result, this ActionScript function (which you’ll create momentarily) will be called.
  3. 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.

Download all the code used in this article.

9 Responses to “Leveraging Zend Framework Components from Adobe Flash Platform Applications”

  1. adaykin Says:

    Channel disconnected before an acknowledgement was received
    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?

  2. rbates Says:

    With respect to this:

    [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.

  3. adaykin Says:

    Hey, thanks for the response,

    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/&quot; + sessionID + ".png";

    My services-config.xml file endpoint uri is pointing to:
    http://localhost/ZendProjects/Vols/gateway.php&quot;

    I am still getting the same errors in my php error log I mentioned in my previous post.

  4. rbates Says:

    Please check to make sure you have the font you are specifying in the PHP file. If you don’t have a batm.ttf font in that folder, it will not work. Due to licensing concerns, I have NOT included fonts. It is unlikely that you have the font I referenced in the example, so you’ll probably need to use one of your own and change the PHP file to reflect this. You can get fonts from your system’s font directory or from here:
    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"

  5. heydan15 Says:

    I just wanted to say thank you for putting together this awesome tutorial. I was very impressed at the ease of setting up a database connection and mail functionality with the Zend Framework.

    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!

  6. adaykin Says:

    Hey I just looked at the comments, and now that I am including the full path I was able to see the image, but when I hit submit for the email I got an error message saying:

    [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

  7. adaykin Says:

    I had the wrong destination property in <mx:RemoteObject> for email and database. In the tutorial there is capitalization where there should not be. Zend_Amf should be zend_amf.

  8. sdemuni Says:

    OK so I’ve checked and double-checked all my code and everything is definitely OK. I’m getting this "Destination ‘zend_amf’ either does not exist…" etc. error and I’m sure it has everything to do with the "Unable to open ‘services-config.xml’" error Zend Studio is spitting out at me.

    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)

  9. baohx2000 Says:

    The "new" version of Flex in beta makes it easier to create data services by allowing you to use a wizard to load your end class and create a gateway automatically (it will also load a copy of ZF to your project, but you can delete it and redo the gateway your own way. Then, instead of mucking about with service-config.xml, you add an endpoint to your mx:RemoteObject tags like this:

    <mx:RemoteObject id="captchaService" destination="Zend_Amf" source="CaptchaService" endpoint="http://localhost/testformflex/gateway.php"&gt;