Creating A New module

The index page should contain a list of posts and link to the detail page - don't worry we'll add that one later. For each post, the title and the author is displayed, as well as the date it was posted and the category it was posted in.

Creating A New module

A module is an organizational unit structuring your applications separate areas of concern. A module structures Actions, Views, Models and Templates into a common subdirectory. It can even bring it's own libraries and some configs specific to this module. Handling the Post and displaying them to the user is one area of concern for our blog, so code related to that should be placed in one module.

Creating a new module is a simple task if we use the agavi commandline script. So navigate to the directory where you created your application, invoke agavi module-wizard and answer the questions.

bloggie$ dev/bin/agavi module-wizard

Module name: Posts

Space-separated list of actions to create for Posts: Index

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

bloggie$ 
      
We created a new module named "Posts" which currently contains a single action named "Index". However, this action is not accessible as there is no route that points to it. We want the list of posts to be on the front page so we just point the default index route to that action. In app/config/routing.xml, look for the route that looks like that: <!-- default action for "/" --> <route name="index" pattern="^/$" module="%actions.default_module%" action="%actions.default_action%" /> and change it to point to the new action:
<!-- default action for "/" -->
<route name="index" pattern="^/$" module="Posts" action="%actions.default_action%" />
        
Done. The "Index" action in the module "Posts" will now be the index page. We could now remove the old IndexAction, but just keep that one for later, we will need it.

Adding a template variable

We want to display a list of posts on the index page. For now, we're just concerned about getting the basic html to work, so we just forget about where to get the data from for the time being. We'll just pass a static array of posts to the template. A post will need the following information to be displayed:

id
The posts id.
title
The posts title.
author_name
The authors name.
posted
The date the post was created.
category_name
The name of the category the post was filed under.
url
The url to the posts detail page. We will leave that one empty but include it so that the attributes are complete.
To pass the information to the template we just need to set it as an attribute in the view or the action. Any attribute set in the global namespace will be available in the template later on. We want to serve the index page on Read (GET) requests so we define the Posts_IndexAction::executeRead() [app/modules/Posts/actions/IndexAction.class.php] as follows - remember that all data retrieval should be done in the action:
public function executeRead(AgaviRequestDataHolder $rd)
{

  $posts = array(
    1 => array(
      'id' => 1,
      'title' => 'First post',
      'posted' => '2008-07-14 00:01:07',
      'category_name' => 'No category',
      'author_name' => 'Admin',
      'url' => null
    ),
    2 => array(
      'id' => 2,
      'title' => 'Second post',
      'posted' => '2008-07-14 00:01:07',
      'category_name' => 'Agavi',
      'author_name' => 'Admin',
      'url' => null
    )
  );

  $this->setAttribute('posts', $posts);

  return 'Success';
}
        
Remember: The Posts_IndexAction::executeRead()method is called any time a Read request is mapped to this action. Please refer to Chapter 1 for a more detailed explanation.
And while we're at it - why don't we change the page title a little, have you noticed the line in the Posts_IndexSuccessView [app/modules/Posts/views/IndexSuccessView.class.php], which reads as:

$this->setAttribute('_title', 'Index');

As you might already have suspected, it sets a template variable that we use to control the displayed title. Don't worry if that seems a little like magic at the moment, we'll show you how that works in a little while. For now just change the line to read:
$this->setAttribute('_title', 'Latest Posts');

Refresh your browser window, and you should now see the change in the title bar and at the top of the page.

Now that we passed in the information about the posts, we adapt the template to make use of this information. As mentioned above, all the attributes we have set will be available in the template variable $t in the template. We want to display the posts as a list, so we change the template app/modules/Posts/templates/IndexSuccess.php to look like in the following listing:
<ul>
<?php foreach ($t['posts'] as $post): ?>
  <li>
    <a href="<?php echo $post['url']; ?>"><?php echo htmlspecialchars($post['title']); ?></a>
    by <?php echo htmlspecialchars($post['author_name']); ?> @ <?php echo $post['posted']; ?> 
    in <?php echo htmlspecialchars($post['category_name']); ?>
  </li>
<?php endforeach; ?>
</ul>
See how we were able to access the attribute 'posts' that we set earlier on in the action by just using $t['posts']? The PHP Renderer we're using just makes all the template variables available as an associative array with their names as key.

There's a few things to note about that template. See how all output is wrapped in a htmlspecialchars call? We want valid XHTML, so we can't use any of the characters that are reserved in HTML. We don't want to escape them before storing the post in a database or the like, because maybe we want to generate output that is not html later on and then we'd have to decode them again. Neither do we want to have the htmlspecialchars call in the view as some templating engines do that implicitly. So converting the special chars in the view would make it harder to just swap in a new templating engine. And we don't want to use htmlentities as we're trying to be XML compliant at the same time. If you have any special characters like umlauts, just use UTF-8 as charset and the regular character. It will save you a lot of hassle later on. If htmlspecialchars is too long to type for you, we recommend having a function with a shorter name like h() or hss() that just wraps the call. For the tutorial, we'll stick with the long version.