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

Custom Validators

Now that our actions don't depend on the data retrieval any more we need to tackle the validation. Currently we're doing a plain check that the post-id is a valid integer in the range of 1-2 but we're depending on the knowledge that the posts are stored in an array indexed by id and that there's just those two ids. If we're to add a third post or change the storage mechanism we'd be in trouble. So we could just relax our validation to check that the id is just a valid positive integer but then we'd need error handling code in the action - a user could just pass an id that does not exist. However, there's an easier way. We just write our own custom validator that does the job for us - don't worry it's a simple thing.

A validator is just a class that is based on AgaviValidator. It needs to implement a single method validate() that either returns true if the validation succeeded or false if it failed. That's about all we need to do, let's get to work. We'll be using the PostManagerModel we created earlier to do the checks. As this validator belongs to the Posts module, we'll place it 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()
  {
    $postId = $this->getData('post');
    
    $manager = $this->getContext()->getModel('PostManager', 'Posts');
    $post = $manager->retrieveById($postId);
    
    if (null == $post)
    {
      $this->throwError();
      return false;
    }
    
    return true;
  }
}

?>

That's all. There's three methods you don't know yet: The getArgument() method retrieves the configured argument name, the getData() method retrieves the request data for the given argument name and the throwError() method emits an error message. Please check the API-documentation for a more detailed description.

But hey, wait. Now we retrieve the post twice, once in the validator to check whether the id is actually valid and once in the action to actually use it. That does not seem reasonable - it duplicates code and even worse, if retrieving the post is a slow operation, it will cost us performance. We wouldn't want that. Good for us, there is a way around that. A validator may export a validated value to any parameter name we choose. This can be the incoming parameter name but it is no requirement. This is done by simply calling the export() method that's defined in AgaviValidator(). This is how our finished validator looks like.

<?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();
    $postId = $this->getData($parameterName);
    
    $manager = $this->getContext()->getModel('PostManager', 'Posts');
    $post = $manager->retrieveById($postId);
    
    if (null == $post)
    {
      $this->throwError();
      return false;
    }
    
    $this->export($post);
    return true;
  }
}

?>

Now we need to make the class known to the framework. This is easily done by adding it to the Post modules autoloader config you'll find in app/modules/Posts/config/autoload.xml. Add the following line to the end of the autoload definitions:

<autoload name="Posts_PostValidator">%core.module_dir%/Posts/lib/validator/PostValidator.class.php</autoload>

The complete file should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations xmlns="http://agavi.org/agavi/config/parts/autoload/1.0" xmlns:ae="http://agavi.org/agavi/config/global/envelope/1.0">
  <ae:configuration>

    <autoload name="BlogPostsBaseAction">%core.module_dir%/Posts/lib/action/BlogPostsBaseAction.class.php</autoload>
    <autoload name="BlogPostsBaseModel">%core.module_dir%/Posts/lib/model/BlogPostsBaseModel.class.php</autoload>
    <autoload name="BlogPostsBaseView">%core.module_dir%/Posts/lib/view/BlogPostsBaseView.class.php</autoload>
    <autoload name="Posts_PostValidator">%core.module_dir%/Posts/lib/validator/PostValidator.class.php</autoload>

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

Now that the class is known to the frameworks autoloader we need to adapt our validation config to use the new validator and configure where to export the post object. We'll just use "post" as name, as the incoming parameter name. Adapt the app/modules/Posts/validate/Post/Show.xml to look like this:

<?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>
      <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>
    
  </ae:configuration>
</ae:configurations>

So, kick out the redundant code from the action and we're done. While we could just leave the Posts_Post_ShowAction::executeRead() method empty and retrieve the post in the view itself it's better practice to do it in the action. If we're to change something about the logic how a post is retrieved, maybe add some checks that the post may or may not be displayed etc, we'd have to change the action and the view as opposed to only changing the action. The Posts_Post_ShowAction::executeRead() [app/modules/Posts/actions/Post/ShowAction.class.php] should now look like this:

public function executeRead(AgaviRequestDataHolder $rd)
{

  $this->setAttribute('post', $rd->getParameter('post'));
  return 'Success';
}