Introduction to MVC Programming with Agavi, Part 1: Open a whole new world with Agavi

Learn to build scalable Web applications with the Agavi framework

This is the first of a five-part series of articles written for the PHP developer interested in learning about an open-source, flexible, and scalable framework called Agavi. In this first article, you walk through the installation of the framework and the other required components, get an overview of Agavi and its functions, and create your first Web application.

Share:

Vikram Vaswani, Founder, Melonfire

Photo of Vikram VaswaniVikram Vaswani is the founder and CEO of Melonfire, a consulting services firm with special expertise in open-source tools and technologies. He is also the author of the books PHP Programming Solutions and PHP: A Beginners Guide.



27 October 2009 (First published 14 July 2009)

Also available in Chinese Russian Japanese Vietnamese

Introduction

Frequently used acronyms

  • API: Application program interface
  • CRUD: Create Read Update Delete
  • CSS: Cascading stylesheet
  • CVS: Concurrent Versions System
  • DNS: Domain Name System
  • HTML: Hypertext Markup Language
  • HTTP: Hypertext Transfer Protocol
  • MVC: Model-View-Controller
  • OOP: Object-oriented programming
  • ORM: Object-Relational Mapping
  • PEAR: PHP Extension and Application Repository
  • RSS: Really Simple Syndication
  • SQL: Structured Query Language
  • SVN: Subversion
  • URL: Uniform Resource Locator
  • XML: Extensible Markup Language
  • XSL: Extensible Stylesheet Language

If you're a serious PHP developer, you already know about (and have probably even used) PHP application development frameworks like Symfony, CakePHP, and the Zend Framework. These frameworks provide a well-rounded API that covers most application requirements and are a solid foundation for PHP application development. You can easily integrate them with third-party libraries and community-development components for additional functionality.

While the frameworks listed above are undoubtedly the most popular, they're not the only games in town; the number of available alternatives increases on a monthly basis. This series focuses on one such alternative: Agavi, a flexible and scalable framework that deserves serious consideration by any professional PHP developer.

In this article series, I'll guide you through the basics of MVC-based application development with Agavi, introduce you to basic framework concepts, and show you how to exploit Agavi's unique approach to quickly and efficiently build a full-fledged Web application from scratch. Through this process, you'll gain an appreciation for the subtleties of this framework, understand the design decisions that make it so secure and extensible, and gain a valuable addition to your PHP development toolkit. So let's get going!


Why choose Agavi?

I'll start with the answer to a very basic question: What is Agavi, and what makes it so special?

In the words of its official Web site, Agavi is "a powerful, scalable PHP5 application framework that follows the MVC paradigm." It provides a comprehensive toolkit to build and deploy PHP-based Web applications, and provides built-in support for security, data caching, internationalization, input validation, and database abstractions. Originally a fork of the Mojavi project, it is currently maintained by Bitextender GmbH, a software company based in Germany, and released to the community under the GNU Lesser General Public License 2.1.

Agavi is interesting for various reasons. Here are my top three:

  1. First, it focuses strongly on code reusability, allowing developers to easily create different interfaces to an application's functionality. This is particularly important in the context of Web applications, which often need to expose both HTML and SOAP interfaces to their internal workings. Suppose, for example, that you wish to build a SOAP interface to your application's existing search engine functionality. With Agavi, this is as simple as defining a new output type, and a renderer that delivers the output of the existing function in a new format. It's not necessary to tweak the existing functionality, and the entire process is transparent and easy to accomplish.
  2. Second, Agavi offers a sophisticated URL routing mechanism, which allows extensive configuration of how URL routes are mapped to application functions. This routing mechanism supports (among other things) optional and mandatory parameters, default values, nested routes, and callback functions. It's easily one of Agavi's most important features. The routing mechanism, like all other aspects of an Agavi application's configuration, is expressed entirely in XML, and Agavi's configuration subsystem allows run-time access to global application settings and variables.
  3. Third, Agavi provides extremely strict request filtering and input validation, out of the box. Filters can be used to pre- and post-process controller methods. Request parameters are validated on each request, and Agavi automatically strips out all unknown parameters, significantly reducing the risk of SQL injection and similar attacks. A number of built-in validators exist for common tasks, such as validation of strings, numbers, timestamps, e-mail addresses, and files. It's also possible to perform validation using regular expressions or define custom validators for situations where the built-in ones aren't sufficient. All these features conspire to make Agavi one of the most secure frameworks around for Web application development.

In addition to the above, Agavi also provides:

  • A conditional caching engine.
  • Support for most common database systems (including MySQL, PostgreSQL, and SQL Server) and ORMs (including Propel and Doctrine)
  • A session management engine
  • Customizable templating; and full compliance with OOP principles.

All in all, it's pretty cool...so dive right in and get started!


Installing Agavi

In this article series, I'll assume that you already have an Apache/PHP/MySQL development environment set up, that you have a working knowledge of PHP and XML, and that you are comfortable using PHP's simple and complex data types. You should also have a reasonable knowledge of general OOP and SOAP concepts, as well as some knowledge of XML tree generation with PHP's Document Object Model (DOM) extension. This series uses PHP V. 5.2.6 and Apache V. 2.2.11.

To begin, create the basic directory structure needed for an Agavi application, using the following steps:

Step 1: Create the application directory structure

Change to the Web server's document root directory (typically /usr/local/apache/htdocs on Linux® or C:\Program Files\Apache\htdocs on Windows®) and create a new subdirectory for the application. For reasons that I will shortly explain, name this directory wasp/.

shell> cd /usr/local/apache/htdocs
shell> mkdir wasp

You will reference the directory throughout this article as $WASP_ROOT.

Within this directory, create another sub-directory named lib/.

shell> cd wasp
shell> mkdir lib

Step 2: Define virtual host settings

For easier access to the application, define a new virtual host and set it to the application's Web root directory. While this step is optional, I recommend it especially when you work on a development machine which holds multiple in-progress applications, as it creates a closer replica of the target deployment environment.

To set up a named virtual host for the application, open the Apache configuration file (httpd.conf or httpd-vhosts.conf) and add the following lines to it:

NameVirtualHost 127.0.0.1
<VirtualHost 127.0.0.1>
    DocumentRoot "/usr/local/apache/htdocs/wasp/pub"
    ServerName wasp.localhost
</VirtualHost>

These lines define a new virtual host, http://wasp.localhost/, whose document root corresponds to the $WASP_ROOT/pub/ directory. Restart the Web server to activate these new settings. Note that you might need to update your network's local DNS server to let it know about the new host as well.

Step 3: Download and install Phing

Agavi makes use of the Phing build system to automatically generate code for actions, views, templates, and validators. Phing requires PHP 5.0 (or higher). The easiest way to install it is with the automated PEAR installer, which should be included by default with your PHP build.

To install Phing, simply issue the following commands at your shell prompt:

shell> pear channel-discover pear.phing.info
shell> pear install phing/phing

The PEAR installer will now connect to the new channel, download the package, and install it to the appropriate location on the system. This article uses Phing V. 2.3.3.

To install the package manually, visit its home page, download the source code archive, and manually uncompress the files to the desired location. For links to the package home page, see Resources in this article. Note that manual installation presupposes some knowledge of PEAR's package organization structure.

Step 4: Download and install Agavi

The next step is to download and add the Agavi framework to the application. At this point, it is also necessary to decide whether the Agavi libraries will be bundled with the application, or whether users will download and install it themselves. Each option has pros and cons:

  • Requiring users to download the Agavi libraries themselves ensures that they always have access to the latest build (with the most recent bug fixes). However, the process can be intimidating for novice users, and if the newer libraries are not backward-compatible with the versions used when you develop the application, unusual and hard-to-track bugs might appear.
  • Bundling the Agavi libraries with the application ensures that users can begin using the application out of the box, with no version incompatibilities. However, it also locks users in to a particular version of Agavi, possibly making it harder to upgrade to newer versions with additional features or necessary bug fixes.

For purposes of this article series, I'll assume that the Agavi libraries are bundled with the application. Therefore, download the latest version of the Agavi framework (this series uses Agavi V 1.0.1) from its official Web site and extract it to a temporary location. Then, copy the extracted src/ directory to $WASP_ROOT/libs/agavi:

shell> cp -R /tmp/1.0.1/src /usr/local/apache/htdocs/wasp/libs/agavi

Also rename and copy the agavi-dist (Linux) or agavi.bat-dist (Windows) file from the extracted bin/ directory to $WASP_ROOT/agavi (Linux) or $WASP_ROOT/agavi.bat (Windows). This is the Agavi build script, which automates many common code creation tasks under the Agavi framework.

shell> cp /tmp/agavi-1.0.1/bin/agavi-dist /usr/local/apache/htdocs/wasp/agavi
shell> chmod +x /usr/local/apache/htdocs/wasp/agavi

Step 5: Configure and test the Agavi build script

The final step is to configure the Agavi build script and tell it the file locations for Agavi on the system. Open this script in a text editor, and update the $AGAVI_SOURCE_DIRECTORY variables with the path (absolute is preferred, but relative will also work) to $WASP_ROOT/libs/agavi. Here's an example of what this might look like on Linux:

SET AGAVI_SOURCE_DIRECTORY="./libs/agavi"

On Windows, you should also set the PHP_EXECUTABLE variable, which defines the complete disk path to the PHP binary. Here's an example:

SET AGAVI_SOURCE_DIRECTORY="./libs/agavi"
SET PHP_EXECUTABLE="C:\Program Files\PHP\php.exe"

Save the file, and then test it by changing to $WASP_ROOT and running the following command:

shell> agavi status
Figure 1. The output of the agavi status command
The output of the agavi status command on Linux

Figure 1 demonstrates an example of the output on Linux . The output lists versions and directory paths for PHP, Phing, Agavi, and existing projects. If you don't see similar output on your system, something went wrong and you'll need to troubleshoot the problem. The Resources in this article includes links to the appropriate sections of the Agavi manual to help you through this process. If, on the other hand, you do see similar output on your system, the foundations of your Agavi application are in place, and you're ready to start adding some code to it!


Starting a new Agavi project

Before you begin, it's worthwhile to understand the example application you'll build. The application is the Web site of a fictional automobile dealership that specializes in the sale of used sports cars. The original plans for the dealership Web site was fairly tame: Some static pages with basic contact and service information, and an enquiry form for visitors to get in touch with sales executives directly. However, over a late lunch, the dealership team came up with an interesting twist: An online used-car classifieds system, which allows sellers to upload pictures and descriptions of their used Ferraris and Lamborghinis, and buyers to search these listings by budget, brand, and keyword. Site moderators have direct access to the uploaded listings, and manually approve suitable vehicles for display in search results. The database of car listings is also available through a SOAP interface, for easy integration into other applications.

They even thought of a cool name for it: The Web Automobiles Sales Platform, or WASP.

The WASP example application is conceived so it covers common requirements encountered in day-to-day application development: static pages, input forms, picture upload, login-protected administration panel, data paging and sorting, multiple output types, and keyword search. Implementing these features requires one to understand the finer details of form processing, input validation, session management, authentication and security, CRUD database operations, Web service APIs, and integration with third-party libraries. As such, it is a good starting point to understand application development with Agavi.

To begin building this application, initialize a new project with the Agavi build script. Change to $WASP_ROOT and run the following command:

shell> agavi project-wizard

Set a name for the project:

Project name [New Agavi Project]: WASP
Project prefix (used, for example, in the project base action) [Wasp]: WASP

Accept default values for all the prompts that follow, except the one below:

Should an Apache .htaccess file with rewrite rules be generated (y/n) [n]? y

The project wizard will now work automatically to create an empty application container and populate it with default settings. Figure 2 has a screen capture of it in action.

Figure 2. The Agavi project wizard in action
Screen capture of the Agavi project wizard in action

As part of the process, the project wizard will add some new directories to your application hierarchy: $WASP_ROOT/app/ for application code; $WASP_ROOT/pub/ for Web-accessible content such as images, stylesheets, and static HTML pages; and $WASP_ROOT/dev/ for development files.

Figure 3 indicates what the end result should look like:

Figure 3. The file layout for an Agavi application
Screen capture of the file layout for an Agavi application

Note that $WASP_ROOT/dev/ contains a copy of the Apache .htaccess file. It's intended specifically for use in projects using a distributed version control system like SVN or CVS, as it allows you to maintain your working copy without polluting the shared workspace. For your current purposes, this directory can safely be deleted; see Resources for a link to the Agavi manual page explaining its utility in greater detail.

Once the project creation process is complete, you can open your Web browser and visit the virtual host you set up earlier. Enter the URL http://wasp.localhost/. You should see an Agavi welcome page, as in Figure 4.

Figure 4. The Welcome page of a new Agavi application
The Welcome page of a new Agavi application

Congratulations! You now have a working (albeit extremely simple) Agavi application up and running. Next, take a closer look at its innards, and then begin modifying it to support the functions discussed earlier.


Understanding basic concepts

Application development in Agavi follows the traditional Model-View-Controller pattern: data models are separated from processing logic, which in turn is separated from the output seen by the user. Agavi implements this pattern through the use of Models, Actions, Views, and Routes.

  • Models represent data and provide functions necessary to manage, manipulate, and perform calculations on this data, which is usually (though not always) stored in a database. Agavi includes adapters for most common databases, as well as drivers for both Propel and Doctrine ORMs.
  • Views represent the output that the user sees. Views are closely coupled with page templates, which contain the layout code or markup necessary to correctly present the view to the user. Views can also assign values to template variables; these values are automatically interpolated into the page templates at run-time.
  • Actions provide the link between Models and Views. They make changes to application data using Models, and then call Views to display the results to the user. An Action can have multiple Views, and can call a different view depending on the result that is to be shown at any given time.
  • Routes provide the link between user requests and Actions. When a user makes a request for an application URL, the routing system intercepts that request and decides, based on the URL pattern, which Action should be invoked to fulfill the request. Routes make use of regular expressions for pattern matching, and are expressed using XML.

Still confused about how these four components fit together? The best explanation of the typical flow of a Web request through an Agavi application is probably given in the Agavi manual itself (see Resources for a link), which states: "When a Web request arrives, the routing mechanism selects the initial Action to be executed; the Action performs necessary changes in application state by calling the Models; it also selects one of its Views to be executed after it finishes. After that, the appointed View performs rendering of the application's output."

By default, all Actions live in the—what else?—Default module. However, you might often wish to group Actions (and their related Models and Views) together based on the different functional areas of your Web application. Modules provide a way to accomplish this. For example, if your application includes subsystems for search, user profile management, and news, you can create separate modules named Search, Profiles, and News, and place the corresponding Actions for each in these modules.

With all this background information at hand, go back to the code generated by the project wizard and take a closer look at how the default Agavi welcome page is generated. First, look inside the application's routing table, stored in $WASP_ROOT/app/config/routing.xml. In Listing 1,you'll find a route for the application index page:

Listing 1. The route for the application index page
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations 
 xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0" 
 xmlns="http://agavi.org/agavi/config/parts/routing/1.0">
  <ae:configuration>
    <routes>    
      <route pattern="" module="Welcome" action="Index" />
      ...
    </routes>
  </ae:configuration>
</ae:configurations>

A route entry typically contains pattern, module, and action attributes. The pattern attribute specifies the URL pattern that the route matches, while the module and action attributes indicate which module and Action to invoke to fulfill the request. By default, Agavi will use the first match it finds; therefore, as a general rule, arrange routes with the most specific ones first and the more general ones further down. To change this behaviour, add a stop=false attribute to the route; this tells Agavi to continue processing even when it finds a match.

The route in Listing 1 is an extremely general one. It ensures that for any URL request, Agavi will invoke the Index Action of the Welcome module. This Action is automatically created (as a PHP class) by the Agavi project wizard, and you'll find it at $WASP_ROOT/app/modules/Welcome/actions/IndexAction.class.php:

Listing 2. The Index Action
<?php
class Welcome_IndexAction extends WASPWelcomeBaseAction
{
  public function getDefaultViewName()
  {
    return 'Success';
  }
}
?>

Every Action class can have some standard methods, and Agavi will use these to determine how to handle different types of requests. For example, an executeRead() method specifies what to do with GET requests, while an executeWrite() method specifies how to handle POST requests. In case neither of these methods are defined, every Action must have, at a minimum, a getDefaultViewName() method, which specifies the default View for the Action. From the code in Listing 2, it's clear that the default View for the IndexAction is named Success.

According to Agavi's file naming conventions, the Success View for an Index Action within the Welcome module should be stored at $WASP_ROOT/app/modules/Welcome/views/IndexSuccessView.class.php. Navigate to the file, and you'll see something like Listing 3:

Listing 3. The Success View
<?php
class Welcome_IndexSuccessView extends WASPWelcomeBaseView
{
  public function executeHtml(AgaviRequestDataHolder $rd)
  {
    $this->setupHtml($rd, 'simple');   
    $this->setAttribute('agavi_release', AgaviConfig::get('agavi.release'));
  }
}
?>

When rendering a View, Agavi automatically calls a method named executeXXX(), where XXX corresponds to the output type required. To generate HTML output, the View must contain an executeHtml() method, which sets up necessary template variables and prepares the template for rendering. As you'll see a little later in this article, Views can also generate output in other formats—for example, to generate XML output, define an executeXml() method, to generate YAML output, define an executeYaml() method, and so on. You can define template variables within the View through the setAttribute() method, and then access the variables within the template itself as keys of the $t array.

It's worth pointing out that the HTML markup to be generated by the View is not stored in the View class file itself, but in a separate template file. As per Agavi convention, this template file is named after the View; therefore, the template for an IndexSuccessView is located at $WASP_ROOT/app/modules/Welcome/templates/IndexSuccess.class.php. Open this file, and you'll see the HTML markup that generates the output shown in Figure 4.

Listing 4. The HTML markup
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" 
 lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <base href="<?php echo $ro->getBaseHref(); ?>" />
    <title>Welcome to Agavi!</title>
  </head>
  <body>
    <div>
      <p>You are running <em><?php echo $t['agavi_release']; ?>
       </em></p>
    </div>
  </body>
</html>

Notice from the code snippet in Listing 3 above how the template variable $t['agavi_release'] is defined within the View using the setAttribute() method, and then dynamically interpolated within the template file itself.

Finally, note that you should store any Web-accessible file assets used by the View, such as image files, JavaScript libraries, or Flash animations, in the application's public area—in this case, $WASP_ROOT/pub/—and referenced appropriately from within the template.

So, here's a quick summary: An HTTP GET request for any application URL is automatically matched, through Agavi's routing subsystem, to the Welcome/IndexAction which, by virtue of the getDefaultViewName() method, knows to invoke the IndexSuccessView. The IndexSuccessView then calls its executeHtml() method to generate HTML output, interpolates a few template variables into the HTML markup, and sends the output back to the client for display.

In this explanation, a couple of things immediately pop out at you:

  1. First, so long as you name and place your Actions and Views correctly, there isn't really very much work for you to do. The framework will automatically locate and execute files for you, without requiring any manual intervention. And, if you find the file-naming convention a bit complex to understand, relax—as you'll see in Serving static content, a wizard takes care of all those details for you!
  2. Second, Routes play a critical role in matching URL requests and directing them to appropriate Actions. This also means that, unlike in some other frameworks, Actions can live anywhere in the hierarchy, and your application URLs need not directly reflect your internal classification of Actions and modules in different categories. Similarly, security and privileges are tied to the Action itself, and not to the module.
  3. Third, a single Action can have multiple Views, and each View can handle different output types. This magnifies the reusability of your code and makes it very easy to, say, add Views for XML or RSS output to the same Action at a later date, as the application evolves.

Setting the application index page and master template

Now that you understand a little more about how Agavi works, you can begin to build the application. While the default Agavi welcome page is very easy on the eyes, it's not necessarily the first thing you want your users to see. Therefore, as a first step, you'll create a new index page for the application.

First, change to $WASP_ROOT and remove the Welcome module that was auto-generated by the project wizard, as below:

shell> rm -rf app/modules/Welcome
shell> rm -rf app/pub/welcome

Also edit the routing table at $WASP_ROOT/app/config/routing.xml, and remove the various entries in it, so that you end up with the empty (for the moment) configuration in Listing 5:

Listing 5. The routing table
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations 
 xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0" 
 xmlns="http://agavi.org/agavi/config/parts/routing/1.0">
  <ae:configuration>
    <routes>

    </routes>
  </ae:configuration>
</ae:configurations>

The project wizard will already have created a Default module containing an IndexAction and an IndexSuccessView; this is a good choice for the application's index page. Open the file $WASP_ROOT/app/modules/Default/actions/IndexAction.class.php in your favourite text editor, and make sure that it contains the lines in Listing 6:

Listing 6. The Default/Index Action definition
<?php
class Default_IndexAction extends WASPDefaultBaseAction
{
  public function getDefaultViewName()
  {
    return 'Success';
  }
}
?>

Next, check that the View file $WASP_ROOT/app/modules/Default/views/IndexSuccessView.class.php exists, and contains the code in Listing 7:

Listing 7. The Default/IndexSuccess View definition
<?php
class Default_IndexSuccessView extends WASPDefaultBaseView
{
  public function executeHtml(AgaviRequestDataHolder $rd)
  {
    $this->setupHtml($rd);
    $this->setAttribute('_title', 'Index');
  }
}
?>

Next, to create the template for the application index page, modify the template file $WASP_ROOT/app/modules/Default/templates/IndexSuccess.php to contain the code in Listing 8:

Listing 8. The Default/IndexSuccess template
Welcome to WASP, your one-stop shop for used sports cars online. 
<p/>
We have a wide selection of used automobiles for your driving pleasure, 
 and all our models are backed with our unique 120-day money-back guarantee.  
<p/>
Use the links above to navigate this web site.

Finally, add an entry to the routing table so that Agavi knows to direct all requests for the index page to Default/IndexAction (Listing 9):

Listing 9. The Default/Index route definition
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations 
 xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0" 
 xmlns="http://agavi.org/agavi/config/parts/routing/1.0">
  <ae:configuration>
    <routes>
      <!-- default action for site root "/" -->
      <route name="index" pattern="^/$" module="Default" action="Index" />
    </routes>
  </ae:configuration>
</ae:configurations>

Now, visit the application index page at http://wasp.localhost/ again in your Web browser, and you should see the revised index page in Figure 5.

Figure 5. The WASP index page
Screen capture of the WASP index page

Yes, I know—it's not very appealing, is it? It definitely needs an image makeover!

When it comes to altering the overall layout of a page, you can go two ways. The first is, obviously, to add the layout markup to the selected page template. However, if you do this, you have to repeat it for each page template in your application, which is both time-consuming and not very update-friendly. A better solution is to change the application's master template, located at $WASP_ROOT/app/templates/Master.php, and have the new layout automatically reflected in all page templates.

Listing 10 shows the code for the revised master template:

Listing 10. The application master template
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <base href="<?php echo $ro->getBaseHref(); ?>" />
    <link rel="stylesheet" type="text/css" href="/css/default.css" />
    <title><?php if(isset($t['_title'])) echo htmlspecialchars($t['_title']) 
     . ' - '; echo AgaviConfig::get('core.app_name'); ?></title>
  </head>
  <body>
    <!-- begin header -->
    <div id="header">
      <div id="logo">
        <img src="/images/logo.jpg" />
      </div>
      <div id="menu">
        <ul>
          <li><a href="#">Home</a></li>
          <li><a href="#">For Sale</a></li>
          <li><a href="#">Other Services</a></li>
          <li><a href="#">About Us</a></li>
          <li><a href="#">Contact Us</a></li>
        </ul>
      </div>
    </div>
    <!-- end header -->
    
    <!-- begin body -->
    <div id="body"> 
      <?php echo $inner; ?>
    </div>
    <!-- end body -->
    
    <!-- begin footer -->
    <div id="footer">
      <p>Powered by <a href="http://www.agavi.org/">Agavi</a>. 
      Licensed under <a href="http://www.creativecommons.org/">Creative Commons</a>.</p>
    </div>
    <!-- end footer -->
  </body>
</html>

You'll notice that the master template makes use of two additional assets—a CSS stylesheet and a logo image. Locate these files in the application's public area, so connecting clients can retrieve them over HTTP. Accordingly, create the css/ and images/ sub-directories within $WASP_ROOT/pub/ and copy the necessary assets to these locations (you'll find them in this article's download archive).

Listing 11 shows the stylesheet rules from $WASP_ROOT/pub/css/default.css, for illustrative purposes:

Listing 11. The application master stylesheet
body { 
  margin: 0; 
  padding: 0; 
  font-family: 'Verdana' sans-serif;
}

#header {
  height: 80px;
  background: #4062A8;
}

#logo {
  float: left;
  padding-left: 50px;
  padding-top: 5px;
}

#menu {
  float: right;
  background: #4062A8;
  margin-right: 20px;
}

#menu ul {
  list-style: none;
}

#menu li {
  margin-top: 35px;
  float: left;  
  padding-right: 25px;
  padding-left: 25px;
}

#menu a, #footer a {
  color: white;
  font-weight: bold;
}

#body {
  padding-top: 20px;
  padding-left: 50px;
  min-height: 375px;
}

#footer {
  font-size: x-small;
  color: white;
  float: right;
  text-align: right;
  background: #4062A8;
  width: 400px;
  clear: both;
  margin-top: 20px;
}

Then, revisit the application's index page, and you should see something that looks like Figure 6:

Figure 6. The revised WASP index page
Screen capture of the revised WASP index page

There you go! Isn't that much nicer to look at?


Serving static content

Thus far, you've only modified existing Actions within the application. However, the Agavi build script makes it very easy to add new Actions to your application. To illustrate, you'll now add a StaticPageAction and corresponding Views, that will be responsible for serving up static content such as the company's "About Us" and "Services" pages. The steps below reflect the standard process you follow to add any new functionality to an Agavi application.

Step 1: Create placeholder classes

Go back to your shell and invoke the Agavi build script as below:

shell> agavi action-wizard

Set up a new Action named StaticContentAction and link it with two Views, StaticContentErrorView and StaticContentSuccessView, by supplying the following values when prompted:

Module name: Default
Action name: StaticContent
Space-separated list of views to create for StaticContent [Success]: Error Success

Agavi will now go to work generating the necessary class files and placing them in the correct locations.

Step 2: Define routes

Once the process is complete, revisit the $WASP_ROOT/app/routing.xml file, and add a new base route for static content that references your newly-minted Action, as in Listing 12:

Listing 12. The Default/StaticContent route definition
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations 
 xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0" 
 xmlns="http://agavi.org/agavi/config/parts/routing/1.0">
  <ae:configuration>
    <routes> 
       ...          
      <!-- action for static pages "/content/$page" -->
      <route name="content" pattern="^/content/(page:[\w-]+)$" module="Default" 
       action="StaticContent" />
 
    </routes>
  </ae:configuration>
</ae:configurations>

This route is a little more complex than the ones you've seen earlier. First, it includes a "name" attribute; this provides a label that can be used when manually generating the route (you'll see this in action a little further along). Second, the regular expression will match URLs in any of the following formats:

/content/hello
/content/HelloWorld
/content/hello-world
/content/Hello-World-123

It should be clear that the last segment in all of the above examples is a variable. It's necessary to tell Agavi's routing subsystem about this, by enclosing that segment of the route definition in parentheses. Using parentheses creates what the Agavi manual refers to as a capture group. Capture groups can be used to automatically convert certain segments of the URL into Agavi variables, which will eventually be validated, placed into an AgaviRequestDataHolder object, and accessed from within an Action or a View.

Step 3: Define validation rules

Defining a route with capture groups is only half the jigsaw, though. For the variables in the route to make it into an Action or View, they must pass Agavi's extremely stringent input validation filter. By default, this filter is set to the strictest level; this means that any GET or POST variables that it doesn't know about (or that fail the specific validation tests) will be automatically dropped and never make it into the application. This extremely strict input sanitization policy is part of what makes an Agavi application so secure.

Agavi comes with a wide range of built-in input validators, which can be configured through a simple XML file. An input validator shares the name of the Action to which it belongs, and can be found in the module's validate/ directory. To see the StaticContentAction validator, edit the file at $WASP_ROOT/app/modules/Default/validate/StaticContent.xml and fill it with the XML in Listing 13:

Listing 13. The Default/StaticContent Action definition
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations
  xmlns="http://agavi.org/agavi/config/parts/validators/1.0"
  xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0"
  parent="%core.module_dir%/Default/config/validators.xml"
>
  <ae:configuration>
    
    <validators method="read">
      <validator class="regex">
        <arguments>
          <argument>page</argument>
        </arguments>
        <ae:parameters>
          <ae:parameter name="pattern">#^[\w-]+$#</ae:parameter>
          <ae:parameter name="match">true</ae:parameter>
        </ae:parameters>
      </validator>
    </validators>
        
  </ae:configuration>
</ae:configurations>

Very simply, this XML block sets up an AgaviRegexValidator for the page variable, and checks that it matches the regular expression specified in validator definition.

As a result of steps 2 and 3, when passed the URL /content/hello, Agavi's routing and validation subsystems automatically check the URL, initialize a AgaviRequestDataHolder variable named page, and assign it the value hello.

Step 4: Write Action code

Why do you want to capture this particular segment of the URL anyway? That's a good question, and the reason will become clear shortly. Until then, though, move forward and set up the StaticContentAction, which you should find at $WASP_ROOT/app/modules/Default/actions/StaticContentAction.class.php. Edit this file so it contains the code in Listing 14:

Listing 14. The Default/StaticContent View definition
<?php
class Default_StaticContentAction extends WASPDefaultBaseAction
{
  public function getDefaultViewName()
  {
    return 'Success';
  }
  
  public function executeRead(AgaviRequestDataHolder $rd)
  {
    return 'Success';
  }  
}
?>

As mentioned earlier, Agavi requires you to explicitly specify the View used to handle different types of requests. For example, an executeRead() method specifies how to handle GET requests, while an executeWrite() method specifies how to handle POST requests. In this case, since the StaticContentAction will only handle GET requests, it must contain an executeRead() method, and this method should reference the StaticContentSuccessView.

It's worth noting that the Success View is invoked only if the input validation tests mentioned earlier are successful. If input validation fails, Agavi default behaviour is to generate the Error View.

Step 5: Write View code

Let's now get to the meat of this section: Setting up the StaticContentSuccessView to serve static pages. Open the view file at $WASP_ROOT/app/modules/Default/views/StaticContentSuccessView.class.php, and fill it with the code in Listing 15:

Listing 15. The Default/StaticContentSuccess View definition
<?php
class Default_StaticContentSuccessView extends WASPDefaultBaseView
{
  public function executeHtml(AgaviRequestDataHolder $rd)
  {
    $this->setupHtml($rd);
    $words = explode('-', $rd->getParameter('page'));
    array_walk($words, create_function('&$i', '$i = ucfirst(strtolower($i));'));
    $tmpl = 'StaticContent' . implode($words);
    if (file_exists(
     dirname($this->getLayer('content')->getResourceStreamIdentifier()) 
     . "/$tmpl.php")) {
      $this->getLayer('content')->setTemplate($tmpl); 
    } else {
      return $this->createForwardContainer(
       AgaviConfig::get('actions.error404_module'), 
       AgaviConfig::get('actions.error404_action'));      
    }
  }
}
?>

This might seem complicated, but it actually isn't. Fundamentally, this View tries to match the URL request to a static page template, and render it if available. First, the View's executeHtml() method retrieves the page variable that was set up by the routing subsystem a few steps ago, using the AgaviRequestDataHolder object's getParameter() method. It then reformats this value according to a pre-defined file naming convention and attempts to find and render a template file with the same name.

For example, if the URL requested by the client was /content/hello-world, the call to $rd->getParameter('page') returns the value hello-world, and the View therefore looks for a template named $WASP_ROOT/StaticContentHelloWorld.php. If the template file exists, the View sends it on to the client; if not, it automatically forwards to Agavi's default Error404 view, which displays a "Page not found" error to the client.

It's also a good idea at this time to set up the StaticContentErrorView, at $WASP_ROOT/app/modules/Default/views/StaticContentErrorView.class.php, to specify the behaviour when input validation fails (Listing 16).

Listing 16. The Default/StaticContentError View definition
<?php
class Default_StaticContentErrorView extends WASPDefaultBaseView
{
  public function executeHtml(AgaviRequestDataHolder $rd)
  {
    return $this->createForwardContainer(
     AgaviConfig::get('actions.error404_module'), 
     AgaviConfig::get('actions.error404_action'));
  }
}
?>

This is pretty simple—you just forward to the default Error404 action again.

Now, all that's left is to actually define the static pages. Look back at Figure 6 (or the template in Listing 10.You'll find two items in the main menu that might reasonably be classified as static content—About Us and Other Services. Create two new template files at $WASP_ROOT/app/modules/Default/templates/StaticContentAboutUs.php and $WASP_ROOT/app/modules/Default/templates/StaticContentOtherServices.php and fill them with some static content, as in Listing 17.

Listing 17. The Default/StaticContentAboutUs template
We've been dealing with used sports cars since 1996, and have built up an 
 enviable reputation for honesty, transparency, and fairness in all our dealings. 
 Rest assured, when you buy a used car from us, you're not just buying an automobile...
 you're buying a solid guarantee of service and quality!
<p/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 
 incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud 
 exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure 
 dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 
 Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt 
 mollit anim id est laborum.
<p/>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 
 incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 
 nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 
 Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu 
 fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 
 culpa qui officia deserunt mollit anim id est laborum.

Then, open your Web browser and visit either http://wasp.localhost/content/about-us or http://wasp.localhost/content/other-services, and you should see your static pages (Figure 7).

Figure 7. A static page
Screen capture of a sample static page, About Us

Step 6: Link things together

You have one minor item left: Linking the items in the main menu to the newly-created static content pages. Open the master template file, and use Agavi's route generator to automatically generate routes to these pages, as in Listing 18.

Listing 18. The updated application master template
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
...
      <div id="menu">
        <ul>
          <li><a href="<?php echo $ro->gen('index'); ?>">
           Home</a></li>
          <li><a href="#">For Sale</a></li>
          <li><a href="<?php echo $ro->gen('content', 
           array('page' => 'other-services')); ?>">Other Services</a>
           </li>
          <li><a href="<?php echo $ro->gen('content', 
           array('page' => 'about-us')); ?>">About Us</a></li>
          <li><a href="#">Contact Us</a></li>
        </ul>
      </div>
...
</html>

Manually generating a route in Agavi is simplicity itself. Within your View or template, use the AgaviRouting object, represented by $ro, to generate the route by calling its gen() method with the route name (remember that name attribute you set up when defining the route?). Route parameters can be specified within an array, and passed to the gen() method as a second argument.

Save your changes to the master template, and revisit your application's index page. The main menu links for the static pages should now be active.

It's worth pointing out, for the purists among you, that there is actually an easier way to serve static pages in an Agavi application: You can simply place them in the application's pub/ directory as HTML files, and link to them manually. However, these pages cannot use Agavi's master template and need their own layout markup, which creates update problems at a later date; also, they don't benefit from Agavi's caching and security mechanisms.


Conclusion

Whew! That's about it for this first article. I introduced you to Agavi, explained its unique features, and guided you through the process of installing and configuring the framework on your development machine. I also explained the basics of how Agavi handles Web requests, and how to use the Agavi build script to quickly set up new projects and class file templates. Finally, I got you started with the WASP example application, which will serve as a testbed application throughout this series.

Your example application is still in its early stages: It knows how to serve up the index page and some static content, and it has a decent master template ready for further development. Implementing even this very basic functionality should give you some insight into the most important components of an Agavi application—routes, actions, views, validators, templates, and modules—and set up the foundation for the more complex functions you'll address in the second part of this article series.

You can download the code archive for the WASP application with the functions discussed to date in this article. I recommend you get it, start playing with it, and maybe try your hand at adding new things to it. I guarantee you won't break anything, and it will definitely add to your learning. Until next time...happy experimenting!


Download

DescriptionNameSize
Archive of the WASP applicationwasp-01.zip3075KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Open source, Web development
ArticleID=412172
ArticleTitle=Introduction to MVC Programming with Agavi, Part 1: Open a whole new world with Agavi
publish-date=10272009