Sr. Web Developer
mediabistro.com
US-NY-New York

Justtechjobs.com Post A Job | Post A Resume

Building a time-tracking and billing application with Adobe AIR and PHP
As a developer, you already know that many times, one programming language, platform, or technology just isn't enough to do the job right. Recently, there's been a lot of buzz and information about using PHP on the server side with Adobe Flex or AIR applications as the front end. You can use this technique to gain a lot of power and flexibility, and that's one of the topics I discuss in depth in this article. Sometimes, however, one interface isn't enough.
In my scenario, this is precisely the case. The scenario calls for a lightweight, cross-platform desktop application that does its job and stays out of the users' way. It also needs a powerful management interface that can be accessed from anywhere. To address both needs, you first create a desktop application for AIR that leverages PHP back-end services for persistent storage and extra horsepower. Then, you create a simple and quick PHP/Hypertext Markup Language (HTML) browser-based interface for management and output applications.
About clocked!
The time-tracking and billing system you create in this article is called Clocked! It is specifically designed for freelancers, contractors, and others who work on computers and need to track their time. To best address the needs of these users, Clocked! has the following requirements:
  • Runs on Mac, Windows, and Linux. Because your users are running all of these platforms in more distributed proportions than regular computer users, you need to support all three.
  • Easily track time in different increments. This feature is key; your users need a simple, intuitive, unobtrusive way to track their time that requires as little effort as possible. This whole project is predicated on the belief that their time is valuable, and you don't want to waste it.
  • Store the information in a central location. Many of your users work in teams or as subcontractors, so information about their hours accrued needs to be accessible from anywhere.
  • Organizes information in a natural way. Your users work on projects for clients and invoice those clients based on hours attributed to each project. The Clocked! application needs to organize all these entities (clients, projects, invoices, and time slips) in a logical way.
The best way to accomplish all these goals at the same time is to divide the feature set into two applications. The Clocked! application consists of a desktop application for time tracking and a web-based, PHP-driven interface for management.
Here's how this two-part approach meets your needs. You take care of the first two requirements at the same time. Because Clocked! runs on AIR, users on Windows, Mac, and Linux can use it. AIR applications also run on the desktop, so users can launch and run the program without opening a separate browser window or tab. Finally, AIR applications run in an Adobe Flash Player-based environment, so you can give your users a sleek, unobtrusive interface with ease.
Because the management side of Clocked! is a web application, managers and users will be able to access Clocked! data from any web browser. Using PHP allows you not only to create a fast, lean application quickly, but you also gain the ability to communicate with the AIR-based client efficiently. The Zend_Amf extension of the Zend Framework allows native Action Message Format (AMF) communication between AIR applications and PHP services, which means that server calls will be as fast as possible, and you'll be able to leverage the full power of PHP when dealing with data from the desktop clients.
Finally, you have to plan carefully to make sure the data structures in Clocked! make sense. You do that by clearly defining all of your objects-clients, projects, invoices, and time slips-and organizing them both in the code and in the MySQL database you'll be using for persistent storage.
Defining the objects
Because invoices, time "tickets," and projects are all predicated to some extent on clients, you first create the Client object. Some incarnation of a client or customer object is present in most applications, so the structure presented in Figure 1 is probably a familiar concept.



Click here for larger image

Figure 1. The Client object structure

This Client object has the standard informational fields, such as an address and contact information, as well as an extra field-rate. The rate property allows users to set different rates for different clients.
Your next object is the Project, and its structure is represented in Figure 2.

Figure 2. The Project object structure
The Project object is associated with its owner through the client_id property. It also has a code property that can be used to store a client's internal reference or project number.
The Ticket object, depicted in Figure 3, is the time- tracking object. It contains the time slips, or tickets, that users submit using the AIR client.



Click here for larger image

Figure 3. The Ticket object structure

The Ticket is the most complex object so far, and it contains several key attributes to help you store, classify, and present them:
  • account_id: Links a ticket to a user
  • category: Used to track how time is spent-for example, development, on-site, or research
  • start_time: A UNIX time stamp indicating when the ticket was first opened
  • end_time: A UNIX time stamp indicating when the ticket was last closed
A ticket's timer can be paused and restarted, so the start and end values refer to the first clock-in and final clock- out for a particular ticket, respectively. All times are represented as UNIX time stamps, or seconds elapsed since midnight, January 1, 1970.
Finally, the Invoice object represents the invoices that will be generated. Its structure is shown in Figure 4.

Figure 4. The Invoice object structure
Setting up the database
For this project, you'll be storing persistent data in a MySQL database. In addition to tables for each object, you need a table for the users. Make sure your users table has at least an ID, user name, and password for each user. To set up your database, create a new empty database called clocked. Then, you can use the clocked.sql script to create the necessary tables and add some sample values.
Creating the PHP services
The first step in setting up the PHP services needed for the Clocked! AIR application is obtaining the Zend Framework. Navigate to the Zend Framework website, and click "Download Now." There are several options on the ensuing page, but for this article, it is assumed that you're using the full Zend Framework version 1.7.3. Figure 5 shows the appropriate download link.



Click here for larger image

Figure 5. Download the Zend Framework
You'll need to sign in to 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 6.



Click here for larger image

Figure 6. Folder structure for the extracted 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; and for MAMP on the Mac, the default web root is Applications/MAMP/htdocs. Within this folder, create a new directory named clocked. This folder will be the base directory for the application. If you're using Windows, copy the library folder from the Zend Framework download (pictured in Figure 6) 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, including Zend_Amf.
Create the folder structure
Now, you need to set up the directory structure of the application. Within the clocked folder, create a new directory called include. Then, open the new include folder and create a folder called services. Within the /services folder, create a folder named VO. Figure 7 illustrates the proper resulting structure.



Figure 7. The Clocked! application's directory structure

Write the PHP services
With the folders in place, its time to write the PHP services, begin by creating a new PHP file in the /VO directory named Ticket.php. You'll use this file to define the Ticket object in PHP:

<?php
class Ticket {
    
public $id;
    
public $duration;
    
public $category;
    
public $details;
    
public $client_id;
    
public $project_id;
    
public $account_id;
    
public $invoice_id;
    
public $start_time;
    
public $end_time;
}
?>
Next, create a service class to expose methods to the AIR client. In the /services folder, create a new file called ClockedService.php. Begin the file by importing the /VO/Ticket.php file and defining the new class:
<?php
include 'VO/Ticket.php';
class ClockedService {
Next, use a comment block or PHPDoc block to describe the first method, saveTicket(), then define the method:
/**
	***description: Saves a new work ticket
	***@param Ticket
	***@return string
	**/
	function saveTicket(Ticket $ticket) {

This method accepts a new Ticket object from the AIR client and returns a string. Next, assign the properties of the passed-in Ticket to local variables. Doing so increases clarity and makes it easier to act on these properties before progressing through the function:

// place ticket attributes into vars in case you
want to act on them somehow
        $duration = $ticket-&gt;duration;
        $category = $ticket-&gt;category;
        $details = $ticket-&gt;details;
        $client_id = $ticket-&gt;client_id;
        $project_id = $ticket-&gt;project_id;
        $account_id = $ticket-&gt;account_id;
        $start_time = $ticket-&gt;start_time;
        $end_time = $ticket-&gt;end_time;
Now, connect to your MySQL database, and build an INSERT query for the new ticket:
	$dbh = mysql_connect('localhost','root','root');
	$sql = 'INSERT INTO `clocked`.`tickets` (`id`, `duration`, 
`category`, `details`, `client_id`, `project_id`, `submitted_time`, 
`invoice_id`, `account_id`, `start_time`, `end_time`) 
VALUES (NULL, ' . $duration . ', ' . $category . ', ' .
 $details . ', ' . $client_id . ', ' . 
Finally, execute the query, and return a string so the client will know that the insertion was successful:
$result = mysql_insert($sql, $dbh);
return 'OK';
}
Now you have the code necessary to insert new tickets that the AIR application submits. But the application also needs two more pieces of information: the list of clients and the list of projects. The lists are used to assign tickets where they belong. When users open a new ticket, they select the client and project from drop-down lists. The getClients() function returns an array of clients to the AIR application:
/**
	***description: returns array of clients
	***@return array
	**/
	function getClients() {
	$sql = 'SELECT * FROM `clocked`.`clients`';
	$dbh = mysql_connect('localhost','root','root');
	$result = mysql_query($sql, $dbh);
	$retVal = array();
		while ($client = mysql_fetch_object($result)) {
		array_push($retVal, $client);	
		}
		return $retVal;	
	}
The client is selected first, so the next step for the ticket application is to get the list of projects belonging to the selected client. The getProjects() function does just that:
/**
	***description: returns a client's projects
	***@param int
	***@return array
	**/
	function getProjects($client_id) {
		$sql = 'SELECT * FROM `clocked`.`projects` 
			WHERE `client_id` = '. $client_id .'';	
		$dbh = mysql_connect('localhost','root','root');
		$result = mysql_query($sql, $dbh);
		$retVal = array();
		while ($project = mysql_fetch_object($result)) {
			array_push($retVal, $project);	
		}
		return $retVal;
	}
Finally, add a login method. This function is like most PHP/MySQL login functions: It accepts an array containing the user-supplied user name and password, then returns the authenticated user's ID to the client. If the login is unsuccessful, it returns the distinctly non-verbose string "bad":
/**
	***description: checks credentials, returns the user's id
	***@param array
	***@return string
	**/
	function logIn($info) {
	    $user = $info[0];
 	    $pass = $info[1];
	    $dbh = mysql_connect('localhost','root','root');
	    $sql = "SELECT * FROM `clocked`.`accounts` 
	      WHERE username = '". $user ."' AND password ='". $pass ."'";
	    $result = mysql_query($sql, $dbh);
	    if (mysql_num_rows($result) > 0) {
			$row = mysql_fetch_object($result);
			$userid = $row->id;
			return id;
		}
		else {
			return "bad";	
		}
	}
}
?>

Remember to close out the class and <?php tag, then save the file.
Make the service available
You now have all the basic parts of a Zend_Amf-compatible service. To make this service available to your applications, you also need a "gateway" file. The gateway file instantiates the Zend_Amf server and passes it information about your services and custom objects. Create the gateway.php file in the main application directory, /clocked. This folder should be inside your web server's web root.
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');
}
This statement adds the location of the Zend Framework to the PHP path variable. Be sure to change this path to reflect where you placed the library folder on your computer. Alternatively, you can permanently add the Zend Framework's location to your PHP path. Next, require_once the Zend_Amf server file and your ClockedService class:
require_once 'Zend/Amf/Server.php';
require_once 'include/services/ClockedService.php';
These next lines instantiate the server and tell it that the ClockedService class should be exposed. The latter is accomplished using the server's setClass() method:
// Instantiate the server
$server = new Zend_Amf_Server();
$server->setClass('ClockedService');
Next, map the Ticket class to the class you'll use in ActionScript on the AIR client. Call it TicketVO. Set this class mapping with the server's setClassMap() method, passing in the ActionScript class name first and the PHP class name second:
$server->setClassMap('TicketVO', 'Ticket');
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 setting is useful during testing, but it 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 $response variable to the server's handle() method to instructs 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 8 shows how your files and folders should now be arranged.



Click here for larger image

Figure 8. The current directory structure

To test your work so far, open a web browser and navigate to http:// localhost/clocked/gateway.php. You should see the words Zend Amf Endpoint. 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 happens, review your code and check your PHP error log for problems. If your test was successful, you're ready to begin creating the desktop component of clocked!
Join us next week as we go through the nuts and bolts of creating the Clocked! desktop client using AIR.