Conditional Form Validation with Zend_Form

A question from ‘ronny stalker’ in the Zend_Form_Element_Multi – Tips and Tricks comments:

I need to do different validations for field A depending on the value of field B and (possibly depending on a variable that is not in the form at all – C ).

in this kind of logic:

if (B ==1)
{
validator_B(A);
}
elseif (C)
{
validator_C(A);
}
else
{
validator_Default(A);
}

I understand that validators get a secondary argument called $context – which can be used to check values of other fields, but how can a validator get knowledge of other variables in the environment?

While this post may not answer ronny’s question exactly, hopefully it will give him a good starting point to get over the hump.

If other, please explain – Conditional Validation Using $context

Many forms have a set of radio buttons, or sometimes a select element, where a user can choose from one of several options.  Sometimes “other”  will be one of those options, with a corresponding “If other, please explain” text field placed directly after.  If “other” is selected, then the accompanying text field is usually required.  Since there’s not a standard Zend Validate validator for this scenario, I’ve written a custom validator that seems to do the trick.

<?php
/**
 * Kendall Extensions
 * 
 * @category Kendall
 * @package  Kendall_Validate
 * @author   Jeremy Kendall 
 */
 
/**
 * @see Zend_Validate_Abstract
 */
require_once 'Zend/Validate/Abstract.php';
 
/**
 * Requires field presence based on provided value of radio element.  
 * 
 * Example would be radio element with Yes, No, Other option, followed by an "If 
 * other, please explain" text area.
 * 
 * IMPORTANT: For this validator to work, allowEmpty must be set to false on 
 * the child element being validated.
 * 
 * From Zend Framework Documentation 15.3: "By default, when an 
 * element is required, a flag, 'allowEmpty', is also true. This means that if 
 * a value evaluating to empty is passed to isValid(), the validators will be 
 * skipped. You can toggle this flag using the accessor setAllowEmpty($flag); 
 * when the flag is false, then if a value is passed, the validators will still 
 * run."
 * 
 * @uses     Zend_Validate_Abstract
 * @category Kendall
 * @package  Kendall_Validate
 * @author   Jeremy Kendall 
 */
class Kendall_Validate_FieldDepends extends Zend_Validate_Abstract {
 
  /**
   * Validation failure message key for when the value of the parent field is an empty string
   */
  const KEY_NOT_FOUND  = 'keyNotFound';
 
  /**
   * Validation failure message key for when the value is an empty string
   */
  const KEY_IS_EMPTY   = 'keyIsEmpty';
 
  /**
   * Validation failure message template definitions
   *
   * @var array
   */
  protected $_messageTemplates = array(
    self::KEY_NOT_FOUND  => 'Parent field does not exist in form input',
    self::KEY_IS_EMPTY   => 'Based on your answer above, this field is required',
  );
 
  /**
   * Key to test against
   *
   * @var string|array
   */
  protected $_contextKey;
 
  /**
   * String to test for
   *
   * @var string
   */
  protected $_testValue;
 
  /**
   * FieldDepends constructor
   *
   * @param string $contextKey Name of parent field to test against
   * @param string $testValue Value of multi option that, if selected, child field required
   */
  public function __construct($contextKey, $testValue = null) {
    $this->setTestValue($testValue);
    $this->setContextKey($contextKey);
  }
 
  /**
   * Defined by Zend_Validate_Interface
   *
   * Wrapper around doValid()
   *
   * @param  string $value
   * @param  array  $context
   * @return boolean
   */
  public function isValid($value, $context = null) {
 
    $contextKey = $this->getContextKey();
 
    // If context key is an array, doValid for each context key
    if (is_array($contextKey)) {
      foreach ($contextKey as $ck) {
        $this->setContextKey($ck);
        if(!$this->doValid($value, $context)) {
          return false;
        }
      }
    } else {
      if(!$this->doValid($value, $context)) {
        return false;
      }
    }
    return true;
  }
 
  /**
   * Returns true if dependant field value is not empty when parent field value
   * indicates that the dependant field is required
   *
   * @param  string $value
   * @param  array  $context
   * @return boolean
   */
  public function doValid($value, $context = null) {
    $testValue  = $this->getTestValue();
    $contextKey = $this->getContextKey();
    $value      = (string) $value;
    $this->_setValue($value);
 
    if ((null === $context) || !is_array($context) || !array_key_exists($contextKey, $context)) {
      $this->_error(self::KEY_NOT_FOUND);
      return false;
    }
 
    if (is_array($context[$contextKey])) {
      $parentField = $context[$contextKey][0];
    } else {
      $parentField = $context[$contextKey];
    }
 
    if ($testValue) {
      if ($testValue == ($parentField) && empty($value)) {
        $this->_error(self::KEY_IS_EMPTY);
        return false;
      }
    } else {
      if (!empty($parentField) && empty($value)) {
        $this->_error(self::KEY_IS_EMPTY);
        return false;
      }
    }
 
    return true;
  }
 
  /**
   * @return string
   */
  protected function getContextKey() {
    return $this->_contextKey;
  }
 
  /**
   * @param string $contextKey
   */
  protected function setContextKey($contextKey) {
    $this->_contextKey = $contextKey;
  }
 
  /**
   * @return string
   */
  protected function getTestValue () {
    return $this->_testValue;
  }
 
  /**
   * @param string $testValue
   */
  protected function setTestValue ($testValue) {
    $this->_testValue = $testValue;
  }
}

The validator above is essentially a conditional NotEmpty validator.  It checks the value of a parent field to see if a child field should be required.  IMPORTANT:  allowEmpty must be set to false on the child field.

Here’s an example of how to use the validator.

// Parent element
$this->addElement('radio', 'flavor', array(
  'required'     => true,
  'label'        => 'Choose a flavor',
  'multiOptions' => array('Vanilla' => 'Vanilla', 'Chocolate' => 'Chocolate', 'Other' => 'Other')
));
 
// Child element. IMPORTANT: allowEmpty must be set to false!
$this->addElement('text', 'flavorOther', array(
  'allowEmpty' => false,
  'label'      => 'If Other, provide flavor here',
  'validators' => array(new Kendall_Validate_FieldDepends('flavor', 'Other')),
));

Again, please note that allowEmpty has been set to false on the child field.  This is necessary to run the FieldDepends validator even when the “If other . . .” element is empty.

While I’m sure there’s plenty of room for refactoring, the above code has served me well.

Adding Validators After Submission but Before Validation

Expanding on the example above, what if it became necessary to add additional validators to the “If other . . .” field?  Because the “If other . . .” field has allowEmpty set to false, and because an empty value is sometimes a valid value, it is not possible to add additional validators that will run only if the field is not empty.  The additional validators will run regardless of the value of the “If other . . .” element, throwing errors when the element is empty.  Additional validators will have to be added somewhere else.

In order to work around this issue, I added a custom method called preValidation() to my form class.

public function preValidation($data) {
 
  if (!empty($data['flavorOther'])) {
    $this->flavorOther->addValidator(new FlavorOther_Validator());
  }
 
  return $data;
}

The preValidation() method is called after submission but before validation.

$form = new Flavor_Form();
 
if (!$this->getRequest()->isPost()) {
  // Display form
  $this->view->form = $form;
  return;
} 
 
$data = $form->preValidation($_POST);
 
if (!$form->isValid($data)) {
  // Failed validation, redisplay form with values and errors
  $this->view->form = $form;
  return;
}
 
 
// Passed validation

While the preValidation() code above adds validation depending on the state of an element in the form, it would be trivial to add validation to the form based on any number of conditions, including conditions that exist as a result of business rules rather than the form’s input.

Wrapping Up

Writing custom validators for the Zend Framework makes server side validation of unique validation scenarios a breeze.  I have yet to encounter a non-standard validation scenario where I haven’t been able to address it by writing a custom validator.  With the ability to extend Zend Form with a couple of helpful custom methods, adding additional validation after form submission becomes trivial.

Have you ever had to write any custom validators?  Any suggestions on improving the code above?  Jump down to the comments and let us know!

UPDATED to add code comments to the validator implementation example. Thanks to reader Neil for the suggestion.

IEs4Linux Blank Screen Bug and Installation Issues

If you want / need to install Internet Explorer in Linux, I highly recommend IEs4Linux by Sérgio Lopes.

IEs4Linux is the simpler way to have Microsoft Internet Explorer running on Linux (or any OS running Wine).

I recently purchased a new Dell 1420n with Ubuntu 7.10 pre-installed. One of the first things I wanted to do was install all of my development tools, including IEs4Linux. What should have been an easy installation turned into a nightmare. I kept running into python errors that aborted the install. Once I finally got the application to install it was unusable. The IE toolbar was missing, including the address bar, the application would frequently crash, and I experienced maddening display bugs.

Apparently the blank screen bug has been a widespread issue. Sérgio has addressed it on his blog and pushed an emergency release of IEs4Linux, version 2.99.0.1, to resolve this issue only.

That’s all well and good, but I still had the python issues to deal with. What finally worked for me was to install IEs4Linux with the –no-gui flag, like so:

$ ./ies4linux --no-gui

For more information on installing IEs4Linux, including IE versions 5 and 5.5, visit the IEs4Linux installation page.

Mail Notification 5.0 with SSL in Ubuntu

The Problem

We recently began using SSL to connect to IMAP at work. Prior to switching to SSL I had been using the excellent Mail Notification to let me know if I had any messages in my inbox. As soon as we switched over to SSL, Mail Notification quit alerting me to the presence of new email.

I figured that all I had to do was change my preferences in Mail Notification and select SSL. Turns out I was right, except I couldn’t select SSL – all of the “SSL/TSL” options were grayed out. Why in the world would that be? After some research I discovered that:

  • SSL isn’t available if the package was built without SSL support (makes sense)
  • The OpenSSL license conflicts with the Debian license

Mail Notification is available in the Ubuntu repository, but without SSL support. Bummer.

Build it Yourself

The resolution is to build Mail Notification with SSL support enabled, but I quickly discovered that building Mail Notification was not as easy as I thought it would be. Although the installation is well documented in the INSTALL file, I still ran into a lot of problems with dependencies.

Dependencies Required

After a lot of troubleshooting and not a little frustration, I came up with a list of dependencies that I needed installed in order to configure and make Mail Notification 5.0 on Ubuntu Gutsy:

  • build-essential
  • gnome-core-devel
  • libnotify-dev
  • libgnome2-dev
  • libgmime-2.0-2-dev
  • libssl-dev

I used the Synaptic Package Manager to install each of these dependencies. Read on if you’re looking to troubleshoot a specific error that you’re running into.

Errors and Troubleshooting

The first time I ran

$ ./configure

I got this error:

checking for C compiler default output file name... 
configure: error: C compiler cannot create executables
See `config.log' for more details.

Installing build-essential took care of the C compiler issue, but I found a new one the next time I ran configure:

Error: checking for GNOME... no
configure: error: unable to find the GNOME libraries

This one drove me a little batty. What does it mean it can’t find the GNOME libraries? I’m running GNOME for Pete’s sake! After a decent amount of hair pulling and a seemingly endless amount of Googling, I finally found that gnome-core-devel, libnotify-dev, and libgnome2-dev resolved the

unable to find the GNOME libraries

error.

Next up was the GMIME error:

checking for GMIME... configure: error: Package requirements (gmime-2.0 >= 2.1.0) were not met:
 
No package 'gmime-2.0' found

At least by now I was making it most of the way through the

configure

process. I found that installing libgmime-2.0-2-dev resolved the issue, finally allowing me to complete the configuration.

Of course, the whole point was to build Mail Notify with SSL support. What do you think I found when configure finally ran all of the way through? Down at the bottom of the options list, I saw this:

--enable-ssl                 no (OpenSSL not found)

By now, I had started seeing a pattern: look up the dependencies, find their dev libraries, install their dev libraries, and voila, on to the next issue. With that in mind, I installed libssl-dev and ran

$ ./configure

one last time. Mail Notify configured without errors and with SSL support. A quick

make

and

make install

later and I had a Mail Notify 5.0 installation complete with SSL support.

Other Options

There seem to be a lot of different ways to skin this particular cat. The solution above is what worked for me, your mileage may vary. You may find it useful to refer to the discussion in this related bug report for background.

For another way to resolve this issue, you might try “How to make a small change to a Debian tool and repackage it.” I didn’t find this article until after I had resolved the issue myself, but it looks like it might be a lot simpler.