Skip to end of metadata
Go to start of metadata
You are viewing an old version of this page. View the current version. Compare with Current  |   View Page History
Under Construction
This proposal is under construction and is not ready for review.

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

Zend Framework: Zend_Validate_Builder, Zend_Filter_Builder Component Proposal

Proposed Component Name Zend_Validate_Builder, Zend_Filter_Builder
Developer Notes http://framework.zend.com/wiki/display/ZFDEV/Zend_Validate_Builder, Zend_Filter_Builder
Proposers Bryce Lohr
Revision 0.1 - Initial version (wiki revision: 16)

Table of Contents

1. Overview

I'd like to propose an alternative to the Zend_Validate_Input proposal. Bill Karwin and others have put a lot of time and energy into that work, and I'd like to take this opportunity to thank them. This work is built upon those, and I'd like to acknowledge that up front.

This proposal is inspired by Christopher Thompson's original Zend_FilterChain proposal, and provides a similar interface. I feel this comes much closer to satisfying the 80/20 rule for most people's validation needs than the previous ones have.

I actually propose quite a bit of change here. Here's a quick rundown to get started:

  • Zend_Filter_Builder and Zend_Validate_Builder classes
  • Facades providing a fluent interface for both Builder classes
  • Several new Zend_Validate_* classes
  • Changes to Zend_Validate_Interface
  • Changes to how Zend_Validate_* deal with error messages

2. References

3. Component Requirements, Constraints, and Acceptance Criteria

  • Zend_Validate_Builder manages exactly one responsibility: validating sets of data in arrays
  • Zend_Filter_Builder manages exactly one responsibility: filtering sets of data in arrays
  • Neither component handles any other aspect of form processing
  • No mechanism for statically calling filters or validators is provided (out of scope)
  • The solution in this proposal is not designed to make using the existing filters and validators more convenient for quickly checking one value; it's geared toward easily handling a lot of data at once
  • This requires some changes to the design of existing Zend_Validate_* classes

4. Dependencies on Other Framework Components

  • Zend_Validate_Interface
  • Zend_Filter_Interface
  • Zend_Validate_*
  • Zend_Filter_*
  • Zend_Exception

5. Theory of Operation

Essentially, Zend_Validate_Builder and Zend_Filter_Builder use the Builder and Composite patterns to construct a recursive tree structure of Zend_Validate_* or Zend_Filter_* instances, which is then applied wholesale to an array of input data. In the case of Zend_Filter_Builder, the result is the original input array after having each of the constituent filters applied to each input array element. In the case of Zend_Validate_Builder, the result is a boolean value indicating whether any single element in the input data was deemed invalid by any of the constituent Zend_Validate_* instances.

Zend_Validate_Builder provides no mechanism for managing error messages. Error messages are a vital part of validating data, however, they have no business in the Controller layer, where Zend_Validate_Builder lives. They belong in the View layer, and are just as important to the presentation of a site as the stylesheet or localization settings. To allow error messages to be managed outside of Zend_Validate_*, I propose some changes to the design of those classes, which are detailed below.

In addition to the Builder classes, I also propose Zend_Validate_Builder_FluentFacade and Zend_Filter_Builder_FluentFacade. These classes provide a very elegant and easy to use fluent interface to the Builder classes, and their use is totally optional. The Builder classes in no way depend on these.

Changes to Zend_Validate_Interface and Zend_Validate_*

In order for the proposed Builder classes to operate, I need to make the following changes to Zend_Validate_Interface. Here's the current code and the new code, with comments stripped out for clarity:

Current Zend_Validate_Interface
New Zend_Validate_Interface

The new interface adds an optional parameter, $field, to the isValid() method, and removes the getMessages() method from the interface. These changes should be completely backward-compatible, since removal of getMessages() from the interface does not require removal from the implementations, and the optional second parameter to isValid() can easily be ignored.

Currently, Zend_Validate_Interface implementations internally create their own error messages, and assign these to a random numeric index in an array. With regard to these, I propose that instead of the numeric indices, specific unique string indices be assigned. These should represent what I refer to as the "reason code". Most Zend_Validate_* classes have only one reason why a value whould fail validation, but some (such as Zend_Validate_EmailAddress) are more complex and have several potential failure reasons. The change I propose here would involved going through all the current Zend_Validate_* classes and manually assigning unique reason codes to each error message they currently generate. This would be done by simply setting the array index the error message is currently assigned to, to the new reason code.

Here's an example validator that preserves all the current functionality, but with the new interface:

Example New Validator

As I mentioned previously, I don't believe error messages should be here anyway, but with this change, you can easily override the default error handler with your own. The error handler will tell you which field failed validation, and why, and you can let your View code map that to the appropriate error message for the user.

Not-so-backward-compatible changes

The most drastic change to the existing Zend_Validate_* classes that I propose here, is to make them all consider omitted fields valid. If the user skipped a field, that field will have the empty string as its value, as opposed to zero or null or something else. Most of the curent Zend_Validate_* classes fail the empty string. The few that pass it, only do so by accident with the correct combination of parameters are given.

I propose that all the existing validators should be modified to explicitly check for the empty string (e.g., $value === '') and return true. Then we can add a new validator, Zend_Validate_NotEmpty, which explicitly fails empty values. This way, particular fields may easily be specified required or optional when validating sets of fields. Additionally, the exact behaviour determining an "empty" field may be user-specified with custom validator.

The biggest downside to this change is that it makes the validators harder to use on their own. Right now you can instantiate a validator, pass it a value, and it will (usually) fail it, if the value is empty. If you're doing this on a required field, then you're done. With this proposed change, you'd have to do two steps to get the same behaviour: instantiate both the NotEmpty validator, and the one you want, and pass your value through both.

Personally, I think this is an acceptable compromise. I think there are probably very few people using the validators on their own, based on the great deal of interest I've seen in a set-based system like I'm proposing here. Also, I should note that in the example above, if you wanted to validate an optional with a stand-alone validator instance, you couldn't do it with the validator alone. You would have to put the validator code inside an if statement that checked for the empty field value.

6. Milestones / Tasks

  • Milestone 1: [DONE] Design interface and test feasibility
  • Milestone 2: Write proposal, gather community feedback
  • Milestone 3: Flesh out proof of concept and revise design based on feedback, if necessary
  • Milestone 4: Develop full implementation and unit tests
  • Milestone 5: Write documentation

7. Class Index

  • Zend_Validate_Builder
  • Zend_Validate_Builder_FluentFacade
  • Zend_Filter_Builder
  • Zend_Filter_Builder_FluentFacade
  • Zend_Validate_NotEmpty
  • Zend_Validate_Equals
  • Zend_Validate_Array
  • Zend_Validate_Switch
  • Zend_Validate_AtMostNofM
  • Zend_Validate_AtLeastNofM

8. Use Cases

For all of these use cases, assume that the code shown is extracted from a valid Action Controller method that is processing a form with the intention of saving the data to the database. Assume that methods referenced after $this-> exist and are valid. In most of these examples, I refer to Zend_Validate_* classes that don't currently exist; for now assume that they do. Some of them, I'm proposing, should be added to the main distribution.

UC-01

Some very basic examples. First, Zend_Filter_Builder:

This filters out all non-digit characters from the phone_no field, and strips HTML tags from the description field. The filter() method takes an array of data, and runs the specified filters on the matching keys.

Next, Zend_Validate_Builder:

This example makes the phone_no field required, and checks that it is numeric. The description field is checked to make sure it's less than 255 characters long. The description field is optional, thanks to the change proposed above, where Zend_Validate_* classes consider empty field values valid.

Note the phone_no example: the order the validators are added to the field is preserved; that is, the $notEmpty validator will be run before the $isNumeric validator. Whether or not the second validator gets run if the first fails is controlled by the optional third argument to addValidator().

UC-02

This demonstrates a little more advanced usage.

Globbing:

Supported Meta-characters
I currently only have support for the '*' meta-character for globbing. However, it's fairly straight-forward to expand the support for more complex matching, including using regular expressions to pick which fields to match. In this implementation, using '*' is semantically equivilent to manually specifying every field in the top level input array.

Grouping:

UC-03

Password confirmation, catpcha validation, etc. This uses the (currently) fictional Zend_Validate_Equals, which validates that the input value is equal to the value given to the constructor. I propose that we add such a validator to the core distribution.

Password Confirmation:

Catpcha validation:

UC-O4

Since this follows the Composite pattern, you can nest validators to do some neat things. In this example, assume we're trying to validate an optional mailing address. If all of the six fields are empty, then we assume the user opted not to give the address. If there are values in any of the fields, the validators will validate them accordingly. This would only work if the validators pass the empty string, as proposed above.

UC-05

Four of the new Zend_Validate_* validators that I'm proposing here are actually validator Decorators. They allow you to do some really useful things, and demonstrate some of impressive flexibility of this design. I'll give a quick sample of each one here. In all of these examples, keep in mind that a Zend_Validate_Builder instance counts as a validator that can be decorated.

Zend_Validate_Array
Most validators take a scalar value to isValid(), and perform checks on that. Zend_Validate_Array takes another validator passed to the constructor, and an array value to isValid(). It will iterate over all the elements of the array, performing the given validator on each one. Obviously, you could nest these as much as you needed to, provided that you know the structure of the input array ahead of time.

Zend_Validate_Switch
Acts like a data-driven switch statement. The constructor takes an associative array of validator instances. The field value passed to its isValid() method is used as the key to look up which validator to run in the array passed to the constructor. This way you can attach different validations to fields based on what the user selects at runtime.

Zend_Validate_AtLeastNofM and Zend_Validate_AtMostNofM
These two are complements of each other, and do basically the same thing. Their constructors take three arguments: a threshold number, a total count, and a validator instance. Basically, they determine whether at least, or at most, n fields of the m total pass the given validator. It's easier to understand by seeing it:

The situations when you actually need to do this are not all that common, but when you need it, this is invaluable. Also remember that you don't have to always use these two together as I have here.

UC-O6

The next two examples are more real-world. I'm writing a financial system which consists of individual applications for General Ledger, Accounts Payable, Accounts Receiveable, etc., along with some applications, such as Privilege Licenses, which are designed for local governments. These examples are adapted from some of the Privilege Licenses code (obviously, it's not the actual application code).

In these classes, I've omitted all but the validate() and handleError() methods for the sake of clarity. Assume the missing methods exist and are valid. Also, these show a good way to keep error messages out of the Controllers and in the Views.

UC-07

This is from a form that allows entry of new License Codes. There are quite a few complex validation requirements on this form. The amount and rate_schedule fields are mutually exclusive. There are seven account number fields, where each account number field actually consists of two fields: one for the Entity (Company) and one for the Account Number. Account numbers are only valid by Entity.

The seven account fields have several complex requirements. The Cash and Revenue accounts are always required. The Due To and Due From accounts are required only if the Cash Ent and Revenue Ent are different, or (if given) the Cash Ent and AR Ent are different. If present, the Cash Ent and Due To Ent must match each other, and the AR Ent, Reserve AR Ent, Due From Ent, and Prepaid Ent must all match.

9. Class Skeletons

]]></ac:plain-text-body></ac:macro>

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.