ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


Simplify PHP Development with WASP


01/19/2006
Add Tag Clouds to Your Site

WASP (Web Application Structure for PHP) is a three-tier framework built on PHP 5. Lately, more software engineers are moving from cumbersome "enterprise" languages such as Java and C# to languages such as Python and Ruby and PHP. With version 5, PHP has finally reached the point where these developers can feel at home in what used to be considered a hacker's language. By demonstrating that it is possible to create and use complicated, "enterprise-class" frameworks effectively in PHP 5, WASP will help more developers make the switch.

WASP's initial basis is in the ideas mentioned in the article "Three-Tier Development with PHP 5." Since then, it has come a long way toward bridging the gap between PHP frameworks and other, traditionally more accepted three-tier tools. PangoMedia has put it to large-scale use in several production applications.

This article demonstrates how to build a small database-driven application: a simple task-list web application. This example is short; however, it demonstrates most of the powerful features that WASP has to offer.

The code in this article uses WASP 1.1. Some familiarity with PHP 5 syntax is helpful, but not necessary. For more information on the methods and classes used in this tutorial, see the WASP API documentation. For additional reference, the following resources may be helpful:

Installation and Configuration

Download and install the latest version of WASP via the PEAR package manager. For detailed instructions, see the WASP Installation and Configuration guide.

Create a WASP project by running the command:

phing -buildfile PEAR_DIR/lib/php/data/WASP/build.xml wasp-project

where PEAR_DIR is the installed location of your PEAR classes. You can find out where this directory is by running:

$ pear config-get php_dir

If all is well, you will see:

Buildfile: /usr/local/php5/lib/php/data/WASP/build.xml
WASP > wasp-project:
Directory to create project in  >

Enter the fully qualified path where your new WASP project will be created. This will create the directory and copy over the build.xml and build.properties files.

For the purposes of this example, I've used /Users/brianfioca/Development/projects/Todo.

Make sure the directory you choose is accessible from within your web server's root.

You should now have a directory called Todo that looks like this:

|_Todo
   |_build.xml
   |_build.properties

WASP uses Phing and the build.properties file to manage its configuration. When you create your application, the Phing tool reads your build.properties and builds the correct configuration files for your project.

Here is a build.properties file that has default values for this tutorial.

build.properties
###################
# FILE: build.properties
# DESC: wasp project configuration properties file
#
# The properties below are used to configure your wasp application.
# Set these properties to customize your wasp install.
#
# run phing config to regenerate the configuration when these are changed.
##
# The name of the application
app.name=       Todo
# Toggle Debug output 
debug.flag=     True
# Toggle for email of error messages
email.flag=     False 
session.flag=  True
# URL of the database for this application
database.url=   mysql://user:pass@localhost/todo
      
# Directory where pear packages are installed
pear.dir=       /usr/local/php5/lib/php

In the configuration, the app.name property is Todo, which will be the name of the application as well as the name of the root directory (located off of the app.dir directory or the wasp directory, if not specified) of all modules, chunks, and templates that the build tool will create.

The app.dir property is the directory where the application will reside within your web root. For this example, I've set the app.dir to /var/www. This is the directory that the Apache HTTP server considers its root directory. Change this directory to match your particular web server configuration. When the application is installed, it'll reside in /var/www/Todo.

Set the pear.dir property to the location of your PEAR classes: the directory that contains DB/DataObject and HTML/Template/Flexy. As I mentioned before, you can also find the location of this directory by running:

$ pear config-get php_dir

Set the database.url property to the URL of the database that your project will use. This example assumes you will be creating a database called todo on the MySQL instance on the local host.

If at any point you wish to regenerate the configuration, run the command:

$ phing config
Building Tag Clouds in Perl and PHP

Essential Reading

Building Tag Clouds in Perl and PHP
By Jim Bumgardner

Tag clouds are everywhere on the web these days. First popularized by the web sites Flickr, Technorati, and del.icio.us, these amorphous clumps of words now appear on a slew of web sites as visual evidence of their membership in the elite corps of "Web 2.0." This PDF analyzes what is and isn't a tag cloud, offers design tips for using them effectively, and then goes on to show how to collect tags and display them in the tag cloud format. Scripts are provided in Perl and PHP.

Yes, some have said tag clouds are a fad. But as you will see, tag clouds, when used properly, have real merits. More importantly, the skills you learn in making your own tag clouds enable you to make other interesting kinds of interfaces that will outlast the mercurial fads of this year or the next.


Read Online--Safari
Search this book on Safari:
 

Code Fragments only

The Database

The next thing to configure is the database model. Because this is an introductory tutorial, I wanted to keep it simple. Here is the SQL needed to create the table for the task list application:

CREATE DATABASE todo;
      
CREATE TABLE `Task` (
      `TaskId` bigint(20) NOT NULL auto_increment,
      `Due` date NOT NULL,
      `Name` text NOT NULL,
      PRIMARY KEY  (`TaskId`)
);

The Task table has three columns. For WASP applications, I strongly recommend that all tables have some kind of id column as a primary key. TaskId is the primary key column. Due is the due date of task, and Name is a column for the name or title of the task.

Writing the Application

Now that you have successfully installed WASP and created the database, it's time to write the code for the task list program.

Model, Views, and Controllers

The next step is to create the model, controllers, and views for the application. To do this, there are build targets in the build.xml that you can use by calling them through Phing.

Don't be scared of the terms used here. "Model" is just a fancy name for the database objects. The "view" is be the HTML code for the pages, and the "controller" is just PHP code that decides what pages to draw and how to handle data from the model.

Create the application and generate the main controller and view objects:

$ phing app

Choose the name Todo when prompted.

This creates the main application directory where your code will reside and execute, as well as the initial model and controller classes for the application.

From within the directory you specified as app.dir (or the current directory, if unspecified) you should now have the following subdirectories:

|_Todo/
   |_templates/
   |_templates_c/

In the Todo/ directory you have the controller classes TodoMainModule.php and TodoMainIndexPage.php, and the controller index.php file.

The templates/ directory is where the view resides. It holds all of the "chunk" or template files. These are where you will write all of the HTML and Flexy code for your application. There is a one to one relationship between Chunk classes and .chunk files. In this case, TodoMainIndexPage extends wasp.gui.Chunk, and refers to the index.chunk template. Don't worry if this doesn't make much sense now; it'll all fall into place in the coming sections.

The templates_c/ directory is where compiled Flexy templates reside. Make sure you grant your web server permission to write to this directory.

Next, create the data model objects.

$ phing db

The directory structure should now look like this:

|_Todo/
   |_db/
   |_templates/
   |_templates_c/

The db/ directory holds all of the model classes. In this case, you simply have TaskWrapper.php. There will be one model class for each table in the database.

You will need a module in the application for creating todo entries, so create the new todo "Entry" module.

$ phing module

Choose the name "Entry" when prompted. The directory tree will now look like:

|_Todo/
   |_db/
   |_templates/
   |_templates_c/
|_Entry/
   |_templates/

There are more controller classes and view chunks in the Entry directories.

Testing the Install

Before you go on, it's important to make sure everything has been installed correctly and that the controllers and views are operating. By default, the application and each module will draw a dummy page with its name if you navigate to them right now.

Start your web server and browse to http://localhost/Todo/ (Note: link will only work if you've followed the tutorial this far.) You should see something like Figure 1.

TodoMain index page
Figure 1. TodoMain index page

You may get the message:

can not write to 'compileDir', which is
    '/path/to/wasp/install/directory/templates_c/'
      Please give write and enter-rights to it

If so, make sure that you have a templates_c directory in your project directory and that it has the correct permissions for your web server to read and write to it. This is the compiled templates directory where Flexy writes all of the compiled PHP code it generates from the .chunk files.

Now point your browser at http://localhost/Todo/Entry/ and make sure it also loads properly.

Entry Module

Now it's time to create the code for the Task Entry page. This is where you'll enter the tasks for your list.

Here's the HTML to use for the template located in Todo/Entry/templates/index.chunk:

 <html>
  <body>
    <form name="entry" method="post">
      <h3>Create Entry</h3>
      <p>
        Name:<br/>
        <input type="text" name="Name"/>
      </p>
      <p>
        Date Due (format mm/dd/yyyy):<br/>
        <input type="text" name="Due"/>
      </p>
      <input type="submit" name="Add" value="Add"/>
    </form>
  </body>
 </html>

Because the Task entries will appear on the Main Todo page, you don't have to even include any data display code here. This page is just a simple form with two text inputs and a submit button.

That's all you have to do for the Entry page. The next time you visit the /Todo/Entry URL you should see Figure 2.

the create entry page
Figure 2. The create entry page

Now that you have the page drawing correctly, plug in the events. The only event you have to handle on this page is the Add button press. You'll need to modify the event handling code to do this. To stick to convention, handle all form processing (and other Event processing) in the handleEvents() method in the Chunk class.

Here's the handleEvents() method for Todo/Entry/EntryIndexPage.php:

protected function handleEvents()
   {
     //Check for Add button presses
     if (Request::getParameter('Add') != null)
     {
         $oTask = new TaskWrapper();
         $oTask->fillFromRequest();
         $oTask->save();
         $this->redirect('../');
     }
   }

The code:

//Check for Add button presses
if (Request::getParameter('Add') != null)
{
  $oTask = new TaskWrapper();
  $oTask->fillFromRequest();
  $oTask->save();
  $this->redirect('../');
}

handles the event of the user submitting the form on the Entry page by pressing the Add button. When this happens, you want to create a new Task with a name and due date specified in the text box on the page. Because the text boxes named Name and Due correspond to the column names in the task table, the lines:

$oTask = new TaskWrapper();
$oTask->fillFromRequest();

construct a new TaskWrapper object and fill it from data submitted in the form. In this case, the Name and Due fields are the only data that applies because this table is small, but you can imagine how easily you could save large amounts of data in this way. Alternately, you could parse the request and manually set the fields in the $oTask object, but in most cases you shouldn't have to. The line:

$oTask->save();

does exactly what you think it should do. Because the TaskWrapper object has no primary key set yet, it saves a new record to the database. If you had constructed it with:

$oTask = new TaskWrapper(12);

and then called save(), it would have updated an existing record with the primary key of 12.

Notice the call to $this->redirect(). This takes you to the main page when you submit an entry so you can see your work. As you haven't written the main page, you won't be able to see your saved tasks just yet.

Main Todo Page

Creating the main page is similar to creating the Entry module page. First, create the template Todo/templates/index.chunk, containing:

  <html>
  <body>
    <h3>{Title}</h3>
    <li flexy:foreach="arTasks,key,task">{task[Name]} - <i>{task[Due]}</i></li>
    <p>
      <a href="Entry/">Add Task</a>
    </p>
  </body>
 </html>

Notice the embedded Flexy code. Pay close attention in the <li> tag at the Flexy property called foreach. Flexy's foreach directive tells the HTML tag for the list item to draw itself in a loop for each value in an array of values passed in. Here that array has the name arTasks. The key and task parameters are the names of the derived variables you'll have access to in the loop.

This is exactly the same as the PHP code:

 <body>
 <?php
    $arTasks = array();
    foreach ($arTasks as $key => $task)
    {
 ?>
        <li><?php echo $task['Name']; ?> - <i><?php echo $task['Due']; ?></i></li>
 <?php
    }
 ?>
 </body>

Already you can see one of the biggest benefits of working with WASP: no need for embedded PHP code. This isolates the HTML from most of the display logic. Most HTML editors parse the flexy: tags seamlessly so that they don't interfere with layout and design.

There's also another type of Flexy tag in the HTML, <h3>{Title}</h3>.

{Title} is a dynamic tag that will be replaced by whatever you set that placeholder to be in the controller class. Here's the draw() method for Todo/TodoMainIndexPage.php:

   public function draw()
   {
       $this->setPlaceholder('Title', 'My Task List');
       $oTasks = new TaskWrapper();
       $oTasks->findAll();
       $arTasks = array();
 
       while ($oTasks->next())
       {
           $arTasks[$oTasks->getId()] = $oTasks->toArray();

           // Reformat timestamp using WASP Dates utility package
           $arTasks[$oTasks->getId()]['Date'] =
               Dates::mysql2datetime($oTasks->getDue());
       }
       
       $this->setPlaceholder('arTasks', $arTasks);
       parent::draw();
   }

Right away you can see how to set the {Title} placeholder. Use the code:

$this->setPlaceholder('Title', 'My Task List);

This lets you dynamically name the task list. You could name it using a field from a different database table if you wanted to. The hard-coded string is here for demonstration purposes only.

In order to fill the arTasks placeholder, you need an array of task records that exist in the database. You have access to this data through WASP's db model layer, using the Wrapper classes that you generated by running phing db and located in the Todo/db/ directory.

The code:

$oTasks = new TaskWrapper();
$oTasks->findAll();

constructs a DataObjectWrapper object for the Task table, and finds all of the existing records.

Now you can loop over the values with the code:

while ($oTasks->next())

The next() method will return false when there are no more records.

While looping through the set of tasks, go ahead and add them to an array to pass to the view:

$arTasks[$oTasks->getId()] = $oTasks->toArray();

This code uses the built-in method getId() to index the array using the primary key of the table you're working with. Referring back to the database-create SQL, this gives the value of the column TaskId. At that index, set the value of the array to the array value of the Wrapper object. The built-in toArray() method returns an array representation of the column data in the table. For example, the array might look something like this:

{ 'TaskId' = '1', 'Name' => 'Buy Groceries'}

The view code doesn't care about the TaskId field, but if you remember, the line:

{task[Name]}

uses the Name field to display the name of the task on the page.

As there isn't a form to process on this page, you don't need a handleEvents() method this time.

Now that you have the code written for the front page of the task list, consider what happens when the page loads. If you haven't saved any task items, there won't be any entries in $arTasks. Because the Flexy placeholder arTasks is null, the block:

<li flexy:foreach="arTasks,key,task">{task[Name]} - <i>{task[Due]}</i></li>

will not display.

Once you go to the Todo/Entry/ page and create the first task, the flexy:foreach has a value to loop over, and the list item will display, as Figure 3 shows.

showing one task
Figure 3. Showing one task

As you create more entries, Flexy automatically displays more list items (Figure 4).

showing multiple list items
Figure 4. Showing multiple list items

Finishing Touches

Congratulations! You now have a functioning task list where you can go and post items for anyone to see. Clearly, however, you have some work to do in the way of tailoring the design to your own style, and adding features such as completion marking and category grouping. Fortunately the HTML is all in the view layer, where you can create and modify templates without fussing over embedded PHP code.

Conclusion

Tools such as DB_DataObjects and Flexy have been available for use in PHP for a while, but few projects have put them together in such a unified way. With the enhanced object-oriented nature of PHP 5, working in three tiers with PHP using WASP is easier. Hopefully this tutorial has provided all of the information you need to go out and create feature-rich dynamic web applications in WASP. For more information on creating applications in WASP, see the WASP documentation.


Return to the PHP DevCenter.

Copyright © 2009 O'Reilly Media, Inc.