1. Introduction
    1. About Agavi
    2. MVC in Agavi
    3. Overview of Agavi
    4. Overview of Application Execution Flow
    5. A Word About Actions
    6. Application filesystem layout
    7. Overview of application configuration
  2. Setting Up The Initial Application
    1. Installing Agavi
    2. Creating an Agavi Project
    3. Finishing The Setup
    4. Finishing The Basic Setup
    5. Installing a New Copy of Your Application
  3. Adding First Code
    1. Creating A New module
    2. Creating A New Action
    3. Tying Things Together — An Introduction To Routing
    4. Fixing The Bloggie Routing
    5. Accessing Request Parameters and Validation Basics
    6. Handling Validation Errors
  4. Putting The M in MVC
    1. Creating A New Model
    2. Adapting The Actions and Views
    3. Custom Validators
  5. Polishing It Up
    1. Layers and Layouts
    2. Applying Our Layout
    3. What Are Slots?
    4. Adding The Post's Title To The URL
    5. Routing Callbacks
    6. Using Callbacks for the Title in URLs
  6. Connecting to a database
    1. The Database Manager
  7. Handling Output Variants
    1. Output Types
    2. Exception Templates
    3. Generating an RSS Feed
  8. Form Processing
    1. Adding a Post
    2. Editing a Post
    3. The Form Population Filter (FPF)
  9. Creating a User Authentication System
  10. Adding To The Master Template

What Are Slots?

Slots are a named placeholder in your template that's tied to the result of a specific action execution. Slots can be used to create reusable components for your project or to split up a page into manageable parts. Slots can be registered at runtime or by using a layout. Slots are registered on a template layer and must have a name that's unique for this layer. All slot output is collected in an associative array named $slots with the slot's name as key.

Slots are executed after the main action's view has been executed but before the template is rendered. The main action's view can feed arbitrary data as parameter to the slot and the slot can access the global request data. The slot's execution environment is otherwise separated from the main actions or any other slots execution environment, most important is that the slot has no access to the main actions template variables - unlike the decorator layers.

Slots are often called "partials" in other frameworks

Registering A Slot In A Layout.

Often slots are used to structure an application and factor out common logic and HTML code from individual actions. A slot that should be available on all pages and does not depend on input from the main action should be registered in the default layout - a standard example for such a slot is the navigation - it usually shares it's logic across all pages and usually only depends on the matched route. Let's extract our static navigation and register it as a slot for our default layout.

A slot is a regular action so we create one. However it's a good idea to move your shared elements to a separate module so we invoke the module wizard:

bloggie$ dev/bin/agavi module-wizard

Module name: Widgets

Space-separated list of actions to create for Widgets: Navigation

Space-separated list of views to create for Navigation [Success]: 

bloggie$ 

So now that we created a new action we need to register it as a slot with ourstandard layout in app/config/output_types.xml:

<!-- standard layout with a content and a decorator layer -->
<layout name="standard">
  <!-- content layer without further params. this means the standard template is used, i.e. the one with the same name as the current view -->
  <layer name="content" />
  <!-- decorator layer with the HTML skeleton, navigation etc; set to a specific template here -->
  <layer name="decorator">
    <ae:parameter name="directory">%core.template_dir%</ae:parameter>
    <ae:parameter name="template">Master</ae:parameter>
    <slots>
      <slot name="navigation" module="Widgets" action="Navigation" />
    </slots>
  </layer>
</layout>

The <slot> tag registers a slot named "navigation" on the decorator layer with the action named "Navigation" located in the module "Widgets". Whenever the decorator layer is rendered this action is executed and the output can be inserted into the layer's rendering. So now we can remove the HTML code for the navigation and place it in the template for the Navigation action. The template app/modules/Widgets/templates/NavigationSuccess.php should now look like this:

<div id="navigation">
  <div class="center_wrapper">

    <ul>
      <li class="current_page_item"><a href="index.html">Home</a></li>
      <li><a href="blog_post.html">Blog Post</a></li>
      <li><a href="style_demo.html">Style Demo</a></li>
      <li><a href="archives.html">Archives</a></li>
      <li><a href="empty_page.html">Empty Page</a></li>
    </ul>

    <div class="clearer">&nbsp;</div>

  </div>
</div>

and the respective part in our decorator template app/templates/Master.php must be replaced by the slot output:

<!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><?php if(isset($t['_title'])) echo htmlspecialchars($t['_title']) . ' - '; echo AgaviConfig::get('core.app_name'); ?></title>
    <link rel="stylesheet" type="text/css" href="css/style.css" />
  </head>
  <body>
  <div id="header">
    <div class="center_wrapper">

      <div id="toplinks">
        <div id="toplinks_inner">
          <a href="#">Sitemap</a> | <a href="#">Privacy Policy</a> | <a href="#">FAQ</a>
        </div>
      </div>
      <div class="clearer">&nbsp;</div>

      <div id="site_title">
        <h1><a href="#">Bloggie</a></h1>
        <p>A demo blog application built on agavi</p>
      </div>

    </div>
  </div>

<?php echo $slots['navigation']; ?>
  
  <div id="main_wrapper_outer">
    <div id="main_wrapper_inner">
      <div class="center_wrapper">

...

Done. Let's start and identify other commonly used elements. There's the header and the footer, the sidebar column and the dashboard at the bottom. Let's start extratcting those - the process is the same as with the navigation, so I'll keep that short. Let's first create four actions:

bloggie$ dev/bin/agavi action-wizard

Module name: Widgets

Action name: Header

Space-separated list of views to create for Header [Success]:

bloggie$
bloggie$ dev/bin/agavi action-wizard

Module name: Widgets

Action name: Footer

Space-separated list of views to create for Footer [Success]:

bloggie$
bloggie$ dev/bin/agavi action-wizard

Module name: Widgets

Action name: Dashboard

Space-separated list of views to create for Dashboard [Success]:

bloggie$
bloggie$ dev/bin/agavi action-wizard

Module name: Widgets

Action name: Sidebar

Space-separated list of views to create for Sidebar [Success]:

bloggie$

Next register the slots in app/config/output_types.xml:

<!-- standard layout with a content and a decorator layer -->
<layout name="standard">
  <!-- content layer without further params. this means the standard template is used, i.e. the one with the same name as the current view -->
  <layer name="content" />
  <!-- decorator layer with the HTML skeleton, navigation etc; set to a specific template here -->
  <layer name="decorator">
    <ae:parameter name="directory">%core.template_dir%</ae:parameter>
    <ae:parameter name="template">Master</ae:parameter>
    <slots>
      <slot name="navigation" module="Widgets" action="Navigation" />
      <slot name="header" module="Widgets" action="Header" />
      <slot name="footer" module="Widgets" action="Footer" />
      <slot name="dashboard" module="Widgets" action="Dashboard" />
      <slot name="sidebar" module="Widgets" action="Sidebar" />
    </slots>
  </layer>
</layout>

and now move the HTML code to the repective templates.

app/modules/Widgets/templates/HeaderSuccess.php:

<div id="header">
  <div class="center_wrapper">

    <div id="toplinks">
      <div id="toplinks_inner">
        <a href="#">Sitemap</a> | <a href="#">Privacy Policy</a> | <a href="#">FAQ</a>
      </div>
    </div>
    <div class="clearer">&nbsp;</div>

    <div id="site_title">
      <h1><a href="#">Bloggie</a></h1>
      <p>A demo blog application built on agavi</p>
    </div>

  </div>
</div>

app/modules/Widgets/templates/FooterSuccess.php:

<div id="footer">
  <div class="center_wrapper">

    <div class="left">
      &copy; 2008 Website.com - Your Website Slogan
    </div>
    <div class="right">
      <a href="http://templates.arcsin.se/">Website template</a> by <a href="http://arcsin.se/">Arcsin</a> 
    </div>

    <div class="clearer">&nbsp;</div>

  </div>
</div>

app/modules/Widgets/templates/DashboardSuccess.php:

<div id="dashboard">
  <div id="dashboard_content">
    <div class="center_wrapper">

      <div class="col3 left">
        <div class="col3_content">

          <h4>Tincidunt</h4>
          <ul>
            <li><a href="#">Consequat molestie</a></li>
            <li><a href="#">Sem justo</a></li>
            <li><a href="#">Semper eros</a></li>
            <li><a href="#">Magna sed purus</a></li>
            <li><a href="#">Tincidunt morbi</a></li>
          </ul>

        </div>
      </div>

      <div class="col3mid left">
        <div class="col3_content">

          <h4>Fermentum</h4>
          <ul>
            <li><a href="#">Semper fermentum</a></li>
            <li><a href="#">Sem justo</a></li>
            <li><a href="#">Magna sed purus</a></li>
            <li><a href="#">Tincidunt nisl</a></li>            
            <li><a href="#">Consequat molestie</a></li>
          </ul>

        </div>
      </div>

      <div class="col3 right">
        <div class="col3_content">

          <h4>Praesent</h4>
          <ul>
            <li><a href="#">Semper lobortis</a></li>
            <li><a href="#">Consequat molestie</a></li>        
            <li><a href="#">Magna sed purus</a></li>
            <li><a href="#">Sem morbi</a></li>
            <li><a href="#">Tincidunt sed</a></li>
          </ul>

        </div>
      </div>

      <div class="clearer">&nbsp;</div>

    </div>
  </div>
</div>

app/modules/Widgets/templates/SidebarSuccess.php:

<div class="right" id="sidebar">

  <div id="sidebar_content">

    <div class="box">

      <div class="box_title">About</div>

      <div class="box_content">
      Aenean sit amet dui at felis lobortis dignissim. Pellentesque risus nibh, feugiat in, convallis id, congue ac, sem. Sed tempor neque in quam.
      </div>

    </div>

    <div class="box">

      <div class="box_title">Categories</div>

      <div class="box_content">
        <ul>
          <li><a href="http://templates.arcsin.se/category/website-templates/">Website Templates</a></li>
          <li><a href="http://templates.arcsin.se/category/wordpress-themes/">Wordpress Themes</a></li>
          <li><a href="http://templates.arcsin.se/professional-templates/">Professional Templates</a></li>
          <li><a href="http://templates.arcsin.se/category/blogger-templates/">Blogger Templates</a></li>
          <li><a href="http://templates.arcsin.se/category/joomla-templates/">Joomla Templates</a></li>
        </ul>
      </div>

    </div>

    <div class="box">

      <div class="box_title">Resources</div>

      <div class="box_content">
        <ul>
          <li><a href="http://templates.arcsin.se/">Arcsin Web Templates</a></li>
          <li><a href="http://www.google.com/">Google</a> - Web Search</li>
          <li><a href="http://www.w3schools.com/">W3Schools</a> - Online Web Tutorials</li>
          <li><a href="http://www.wordpress.org/">WordPress</a> - Blog Platform</li>
          <li><a href="http://cakephp.org/">CakePHP</a> - PHP Framework</li>
        </ul>
      </div>

    </div>

    <div class="box">

      <div class="box_title">Gallery</div>

      <div class="box_content">

        <div class="thumbnails">

          <a href="#" class="thumb"><img src="sample-thumbnail.jpg" width="75" height="75" alt="" /></a>
          <a href="#" class="thumb"><img src="sample-thumbnail.jpg" width="75" height="75" alt="" /></a>
          <a href="#" class="thumb"><img src="sample-thumbnail.jpg" width="75" height="75" alt="" /></a>
          <a href="#" class="thumb"><img src="sample-thumbnail.jpg" width="75" height="75" alt="" /></a>
          <a href="#" class="thumb"><img src="sample-thumbnail.jpg" width="75" height="75" alt="" /></a>
          <a href="#" class="thumb"><img src="sample-thumbnail.jpg" width="75" height="75" alt="" /></a>

          <div class="clearer">&nbsp;</div>

        </div>

      </div>

    </div>

  </div>

</div>

And finally our modified app/modules/templates/Master.php:

<!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><?php if(isset($t['_title'])) echo htmlspecialchars($t['_title']) . ' - '; echo AgaviConfig::get('core.app_name'); ?></title>
    <link rel="stylesheet" type="text/css" href="css/style.css" />
  </head>
  <body>

<?php echo $slots['header']; ?>
<?php echo $slots['navigation']; ?>
  
  <div id="main_wrapper_outer">
    <div id="main_wrapper_inner">
      <div class="center_wrapper">

        <div class="left" id="main">
          <div id="main_content">
            
            <!-- this is where the main content will be placed -->
            <?php echo $inner; ?>
            
          </div>
        </div>

        <?php echo $slots['sidebar']; ?>

        <div class="clearer">&nbsp;</div>

      </div>
    </div>
  </div>

  <?php echo $slots['dashboard']; ?>

  <?php echo $slots['footer']; ?>
  </body>
</html>

That's far shorter and nicely displays how the whole page is constructed from small elements. Looks like we have everything accounted for, do we? No, wait.

Dynamically Registering a Slot in a View

There's another set of elements that are very similar. Let's have a look at the display of posts on the front page and on the post's detail page. Overall, they look pretty similar, however theres some minor differences:


  • The number of comments is listed on the frontpage, on the detail page we want to display the comments themselves.
  • The headline tag is h2 on the frontpage while it is h1 on the detail page.
  • The headline is linked on the frontpage while it is not on the detail page.

Still, we'd really like to factor that out. If we could only use the detail page for the list display as well. Well, it turns out we can. A view can register arbitrary slots on any defined layer and pass any parameter it wishes. Here's how it works. We change our app/modules/Posts/views/IndexSuccessView.class.php to register the slots on it's main layer which is named "content":

<?php

class Posts_IndexSuccessView extends BlogPostsBaseView
{
  public function executeHtml(AgaviRequestDataHolder $rd)
  {
    $this->setupHtml($rd);
    
    $contentLayer = $this->getLayer('content');

    $ro = $this->getContext()->getRouting();
    
    $posts = array();
    
    foreach($this->getAttribute('posts') as $p)
    {
      /* register one slot per post */
      $contentLayer->setSlot('post'.$p->getId(), $this->createSlotContainer('Posts', 'Post.Show', array('post' => $p->getId())));
    }
    
    $this->setAttribute('_title', 'Latest Posts');
  }
}

?>

and then change the template in app/modules/Posts/templates/IndexSuccess.php to just display the slots:

<?php
foreach ($slots as $slot)
{
  echo $slot;
}
?>

The call to AgaviView::getLayer() retrieves the layer named 'content' from the list of configured layers. The call to AgaviView::createSlotContainer() creates a new slotcontainer that is then registered on the layer via AgaviTemplateLayer::setSlot(). We create a unique name for each slot we're creating and we're passing the post-id as parameter when creating the slot.

That takes care of most of our problems - if you have a look at the index page you'll see that the list of posts is nicely displayed - with the mentioned drawbacks. The headline is <h1> and not <h2>, the number of comments not shown. Let's take care of that.

We need a way to distinguish where we are - is the detail page included as a slot or is it the main action. We could either pass in a new parameter or find something else. Well, there is already something else. Each action execution is isolated in an AgaviExecutionContainer and it does have access to that container. Creating a slot container automagically sets a parameter on the container and we can read that parameter in our view and react accordingly (app/modules/Posts/views/Post/ShowSuccessView.class.php):

<?php

class Posts_Post_ShowSuccessView extends BlogPostsBaseView
{
  public function executeHtml(AgaviRequestDataHolder $rd)
  {
    $this->setupHtml($rd);
    
    $p = $this->getAttribute('post');
    $post = $p->toArray();
    $post['url'] = $this->getContext()->getRouting()->gen('posts.post.show', array('post' => $p->getId()));
    
    $this->setAttribute('post', $post);
    
    $isList = $this->getContainer()->getParameter('is_slot', false);
    
    if($isList) {
      $headlineSize = 2;
      $linkHeadline = true;
      $displayComments = false;
    } else {
      $headlineSize = 1;
      $linkHeadline = false;
      $displayComments = true;
    }
    
    $this->setAttribute('headline_size', $headlineSize);
    $this->setAttribute('link_headline', $linkHeadline);
    $this->setAttribute('display_comments', $displayComments);
    
    $this->setAttribute('_title', $post['title']);
  }
}
?>

Now we only need to adapt the template in app/modules/Posts/templates/Post/ShowSuccess.php accordingly and we're all set:

<?php
// alias the post, to make access shorter
$post = $t['post'];

$headline = htmlspecialchars($post['title']);

if($t['link_headline']) {
  $headline = sprintf(
    '<a href="%1$s">%2$s</a>',
    $post['url'],
    $headline
  );
}

$headline = sprintf(
  '<h%1$s>%2$s</h%1$s>',
  $t['headline_size'],
  $headline
);

?>
<div class="post">

  <div class="post_title"><?php echo $headline; ?></div>
  <div class="post_date">Posted on <?php htmlspecialchars($post['posted']); ?> by <a href="#"><?php echo htmlspecialchars($post['author_name']); ?></a></div>
  
  <div class="post_body">
    <?php echo $post['content']; ?>
  </div>
  
  <div class="post_meta">
    <?php
  if(!$t['display_comments']) {
    ?>
    <a href="#">5 comments</a> | 
    <?php
  }
    ?>
    Tagged: <a href="#"><?php echo htmlspecialchars($post['category_name']); ?></a>
  </div>

</div>

Done. Fire up your browser and check - things should be just fine.

Slots, Request Data access and Validation

The usual rules for validation apply for slots as well: No access to any piece of request data unless it's validated. This includes any kind of request data, the global data passed by the client and the additional arguments passed when registering the slot. The above piece of code only works because we already have validation defined for the parameter named "post".

However, there is an exception: Slots that are marked as "simple" have full access to all arguments that were passed at registration time even without validation. Simple slots however have no access at all to the global request data passed by the client.

Slots and Layouts

Slots can use layouts themselves. However, there's some things that you need to take into account. If slot A uses a layout that includes slot B and slot B uses a layout that includes slot A you'll get an infinite loop. That's called a layout recusion. If you ever get an error message saying "Too many execution runs detected" chances are high that you've just created a layout recursion.

The generated projectspecific BaseView contains some minimal protection against layout recursions: In case it detects that it's run inside a slot it attempts to load a different standard layout - the one named 'slot' instead of the configured default.

Slots and Performance

On the first sight slots lower the performance. They introduce some management overhead, consume a little memory for a couple of extra objects and so on. However benchmarks show that the overhead is very low and is easily countered by increased developer productivity (remember that developer time is way more expensive than cpu time). There are also other effects that counter the overhead introduced by slots. Slots can be individually cached by Agavi, each one with it's own set of rules and cache times. This raises performance by a far larger margin than the initial overhead. Pages using slots scale far better than pages without.

However, there are some things to keep in mind when splitting up your layouts into slots:


  • Often slots are used for display purposes only and have no own logic. The actions belonging to those slots should be marked simple to reduce the overhead to the lowest possible level. This is the case for our widgets - Header, Footer, Navigation, Dashboard and Sidebar slots.
  • Be careful with validation, running full validation each time you call a slot may bring your page to a grinding halt, especially when the validation is an expensive operation such as contacting the database or a webservice.

So let's check our application. We haven't marked our Widgets as simple, so we should do that now but instead of implementing isSimple() on all of those four classes we just go to our app/modules/Widgets/lib/action/BlogWidgetsBaseAction.class.php and implement it there. All actions in the Widgets module extend this base class and we can safely assume that pretty much all Widgets are for display only and thus never will contain any business logic - if an exception comes along we can still overwrite the behavior in that specific class.

<?php

/**
 * The base action from which all Widgets module actions inherit.
 */
class BlogWidgetsBaseAction extends BlogBaseAction
{

  /**
   * Whether or not this action is "simple", i.e. doesn't use validation etc.
   *
   * @return     bool true, if this action should act in simple mode, or false.
   */
  public function isSimple()
  {
    return false;
  }
  
}

?>

That takes care of disabling all validation and action execution for all Widgets. That validation topic however is a bit a tricky one. Think about it: our IndexAction does a query to retrieve a list of valid posts and then, when the slots are executed the validation once again does a query to check each single one. We're just doing array lookups so far, that's fast but what if we'd have to connect to a SOAP service that needs 0.5 seconds for a reply? That would be 5 seconds overhead for a list of 10 items, that's way to much and it's completely wasted, we ourselves pass the post and we do know it's valid.

We could perhaps implement our Posts_PostShowAction::isSimple() [app/modules/Posts/actions/Posts/ShowAction.class.php] in a way that returns true if the action is called in a slot and false if not. Something along the lines of

  /**
   * Whether or not this action is "simple", i.e. doesn't use validation etc.
   *
   * @return     bool true, if this action should act in simple mode, or false.
   */
  public function isSimple()
  {
    if($this->getContainer()->getParameter('is_slot', false))
    {
      return true;
    }
    return false;
  }
  

That's a technique that works often but it's not going to help us here - we do some work in our executeRead() method and marking the action as "simple" would skip that. But we can change our validation. We modify our validator so that if the parameter is already an object type PostModel it just short-circuits and returns true. No client could ever pass a valid object via GET or POST so it must have been passed from within the framework. And if we ourselves pass an invalid object, well then no validation is going to safe us. Let's change the validator in app/modules/Posts/lib/validator/PostValidator.class.php:

<?php

class Posts_PostValidator extends AgaviValidator
{
  /**
   * Validates the input
   * 
   * @return     bool The input is valid number according to given parameters.
   */
  protected function validate()
  {
    $parameterName = $this->getArgument();
    $data = $this->getData($parameterName);
    
    if($data instanceof Posts_PostModel) {
      $post = $data;
    } else {
      $manager = $this->getContext()->getModel('PostManager', 'Posts');
      $post = $manager->retrieveById($data);

      if (null == $post)
      {
        $this->throwError();
        return false;
      }
    }
    
    $this->export($post);
    return true;
  }
}

?>

Now we need to change the Posts_PostIndexSuccessView::executeHtml() [app/modules/Posts/views/IndexSuccessView.class.php] to pass a full Posts_PostModel object instead of the id:

<?php

class Posts_IndexSuccessView extends BlogPostsBaseView
{
  public function executeHtml(AgaviRequestDataHolder $rd)
  {
    $this->setupHtml($rd);
    
    $contentLayer = $this->getLayer('content');

    $ro = $this->getContext()->getRouting();
    
    $posts = array();
    
    foreach($this->getAttribute('posts') as $p)
    {
      /* register one slot per post */
      $contentLayer->setSlot('post'.$p->getId(), $this->createSlotContainer('Posts', 'Post.Show', array('post' => $p)));
    }
    
    $this->setAttribute('_title', 'Latest Posts');
  }
}

?>

and that's it. While this does not remove the full validation it still removes most of the overhead introduced by it. This technique is often used on dual-purpose actions.