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

Editing a Post

To demonstrate the ability to use the Form Population Filter (FPF), we're going to make an edit post page. So to start, we need to create the action, views and templates.
bloggie$ dev/bin/agavi action-wizard
          
Module name: Posts
          
Action name: Post.Edit
          
Space-separated list of views to create for Add [Success]: Input Success Error
          
bloggie$
          
The Action has nothing special in it, other than a new method for the Posts_PostManager model, it does nothing more than update the database row for the post and the getDefaultViewName() has been replaced with executeRead() so the validation would work.
<?php

class Posts_Post_EditAction extends BlogPostsBaseAction
{
  /**
   * Serves Write (POST) requests.
   * 
   * @param      AgaviRequestDataHolder the incoming request data
   * 
   * @return     mixed <ul>
   *                     <li>A string containing the view name associated
   *                     with this action; or</li>
   *                     <li>An array with two indices: the parent module
   *                     of the view to be executed and the view to be
   *                     executed.</li>
   *                   </ul>
   */
  public function executeWrite(AgaviRequestDataHolder $rd)
  {
    $post = $rd->getParameter('post');
  
    $post->setTitle($rd->getParameter('title'));
    $post->setCategoryId($rd->getParameter('category'));
    $post->setContent($rd->getParameter('content'));
  
    $postManager = $this->getContext()->getModel('PostManager', 'Posts');
    $postManager->storeEdit($post);
  
    return 'Success';
  }

  public function executeRead(AgaviRequestDataHolder $rd)
  {
    return 'Input';
  }

  public function isSecure()
  {
    return true;
  }
}

?>
      
As pointed out in the action, there's a new method in the Posts_PostManager called storeEdit(), it's a rather simple update query being executed.
public function storeEdit(Posts_PostModel $post) 
{
  $con = $this->getContext()->getDatabaseManager()->getDatabase()->getConnection();
  
  $sql = 'UPDATE posts SET title=?, category_id=?, content=? WHERE id=?';
  
  $stmt = $con->prepare($sql);
  
  $stmt->bindValue(1, $post->getTitle(), PDO::PARAM_STR);
  $stmt->bindValue(2, $post->getCategoryId(), PDO::PARAM_INT);
  $stmt->bindValue(3, $post->getContent(), PDO::PARAM_STR);
  $stmt->bindValue(4, $post->getId(), PDO::PARAM_INT);
  
  $stmt->execute();
  
  return $stmt->execute();
}
The validation code is not really any different from the experiences you have had with it before, except it's a mixture between the add action and the show action
<?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%/Posts/config/validators.xml"
>
  <ae:configuration>
    <validators method="read">
      <validator class="Posts_PostValidator" name="post">
        <arguments>
          <argument>post</argument>
        </arguments>
        <errors>
          <error>The parameter post must contain a valid post id</error>
        </errors>
        <ae:parameters>
          <ae:parameter name="export">post</ae:parameter>
        </ae:parameters>
      </validator>
    </validators>
  
    <validators method="write">
      <validator class="Posts_PostValidator" name="post">
        <arguments>
          <argument>post</argument>
        </arguments>
        <errors>
          <error>The parameter post must contain a valid post id</error>
        </errors>
        <ae:parameters>
          <ae:parameter name="export">post</ae:parameter>
        </ae:parameters>
      </validator>
    
      <validator class="string">
        <arguments>
          <argument>title</argument>
        </arguments>
        <errors>
          <error>The title field has an invalid value.</error>
          <error for="required">Please provide a title.</error>
          <error for="max_error">The title must be shorter than 255 characters.</error>
        </errors>
        <ae:parameters>
          <ae:parameter name="max">255</ae:parameter>
        </ae:parameters>
      </validator>
    
      <validator class="string">
        <arguments>
          <argument>content</argument>
        </arguments>
        <errors>
          <error>The content field has an invalid value.</error>
          <error for="required">Please provide a post body.</error>
          <error for="max_error">The post body must be shorter than 65536 characters.</error>
        </errors>
        <ae:parameters>
          <ae:parameter name="max">65536</ae:parameter>
        </ae:parameters>
      </validator>
    
      <validator class="inarray">
        <arguments>
          <argument>category</argument>
        </arguments>
        <errors>
          <error>Please choose a valid category.</error>
          <error for="required">Please choose a category.</error>
        </errors>
        <ae:parameters>
          <ae:parameter name="values">
            <ae:parameters>
              <ae:parameter>1</ae:parameter>
              <ae:parameter>2</ae:parameter>
            </ae:parameters>
          </ae:parameter>
        </ae:parameters>
      </validator>
    </validators>
  
  </ae:configuration>
</ae:configurations>
The code used for this section isn't anything special until you get to the Posts_Post_EditInputView, where we introduce a new feature to the project. The FPF code is rather simplistic and is rather easy to use the code below simply creates a new AgaviParameterHolder and uses the form control names as the keys, the values for these keys is what is going to be shown in the form's control value attribute. We add the AgaviParameterHolder object as an attribute directory to the request object with the namespace "org.agavi.filter.FormPopulationFilter" so Agavi knows what it is.
<?php

class Posts_Post_EditInputView extends BlogPostsBaseView
{
  public function executeHtml(AgaviRequestDataHolder $rd)
  {
    $this->setupHtml($rd);

    $post = $rd->getParameter('post');

    $form = new AgaviParameterHolder(array(
      'post' => $post->getId(),
      'title' => $post->getTitle(),
      'content' => $post->getContent(),
      'category' => $post->getCategoryId(),
    ));

    $this->setAttribute('_title', 'Edit Post');
    $this->setAttribute('post', $post);
    $this->getContext()->getRequest()->setAttribute('populate', array('edit' => $form), 'org.agavi.filter.FormPopulationFilter');
  }
}

?>
And the corresponding template, this is the same code for the add template but with an ID set on the form:
<form id="edit" action="<?php echo $ro->gen('posts.post.edit', array('post' => $t['post'])); ?>" method="post">
  <fieldset>
    <div class="form_row">
      <label for="input_title">Title:</label>
      <input type="text" name="title" id="input_title" />
    </div>
    <div class="form_row">
      <label for="input_content">Content:</label>
      <textarea name="content" id="input_content"></textarea>
    </div>
    <div class="form_row">
      <label for="input_category">Category:</label>
      <select name="category" id="input_category">
        <option value="1">No category</option>
        <option value="2">Agavi</option>
      </select>
    </div>
    <div class="form_row form_row_submit">
      <input type="hidden" name="post" />
      <button type="submit" class="submit">Add Post</button>
    </div>
  </fieldset>
</form>

The Success view is no different from the view used for Posts_AddSuccessView.

To make the page actually viewable, we need to add a line to the app/config/routing.xml, so the "Post" block now looks like this:
<route name=".post" pattern="^/(post:\d+)(-{title:[-\w]+})?" action="Post">
  <callbacks>
    <callback class="PostRoutingCallback" />
  </callbacks>
  
  <route name=".show" pattern="^$" action=".Show" />
  <route name=".edit" pattern="^/edit$" action=".Edit" />
</route>