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

<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: 32)

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
  • An Error Manager class to faciliate configuring error messages for many validators at once

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
  • Zend_Validate_Builder_ErrorManager is responsible for defining and reporting error messages for validators
  • 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
  • Thanks to the pre-1.0 changes to the Zend_Validate_* classes with regards to error messages, this proposal works with the existing validation API as-is.

4. Dependencies on Other Framework Components

  • Zend_Validate_Interface
  • Zend_Filter_Interface
  • Zend_Validate_*
  • Zend_Filter_*
  • Zend_Validate_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.

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.

Error messages are a vital part of data validation. If your code decides that something the user entered is not acceptable, you must be able to tell the user why. To that end, I propose a Zend_Validate_Builder_ErrorManager class. This class provides a way for the Zend_Validate_* classes, Zend_Validate_Builder, and your application code to define and communicate errors. The Zend_Validate_* classes all have built-in error messages which, for a variety of reasons, are often not suitable to display directly to users. Therefore, the error messages for validators can be overridden. However, this can only be done on an individual, validator-by-validator basis. Zend_Validate_Builder_ErrorManager allows you to define all the error messages for any arbitrary set of validators, by data field. This way, you can easily set up each error message for a field, and also easily retreive any error messages by field for display.

6. Milestones / Tasks

  • Milestone 1: [DONE] Design interface and test feasibility
  • Milestone 2: [DONE] Write proposal
  • Milestone 3: [IN PROGRESS] Gather feedback and revise design as necessary
  • Milestone 4: [IN PROGRESS] Develop full implementation and unit tests
  • Milestone 5: Write documentation

7. Class Index

  • Zend_Validate_Builder
  • Zend_Validate_Builder_FluentFacade
  • Zend_Validate_Builder_FluentAdder
  • Zend_Validate_Builder_ValidatorFactory
  • Zend_Validate_Builder_ValidatorFactory_Interface
  • Zend_Validate_Builder_ErrorManager
  • Zend_Validate_Builder_ErrorManager_Interface
  • Zend_Filter_Builder
  • Zend_Filter_Builder_FluentFacade
  • Zend_Filter_Builder_FluentAdder
  • Zend_Filter_Builder_FilterFactory
  • Zend_Filter_Builder_FilterFactory_Interface

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. I plan to add a separate proposal for some new validators that would work well with this proposal, as well as being useful in general.

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:

Here, the phone_no field is checked to make sure it's numeric, and description field is checked to make sure it's less than 255 characters long. Since the Zend_Validate_* classes will usually fail an empty value, all fields are required by default. The addValidator method takes an optional third argument, which are flags for that specific field. We're passing the OPTIONAL flag for the description field, so Zend_Validate_Builder will skip over it when it's empty, and the StringLength validator will never see it unless it has a value.

Note the phone_no example: the order the validators are added to the field is preserved; that is, the IsNumeric validator will be run before the Between validator. Whether or not the second validator gets run if the first fails is controlled by the BREAKCHAIN field flag. Arguments to the optional third parameter can be bitwise OR'ed together to combine effects.

BREAKCHAIN
The BREAKCHAIN flag is sort of a hold over from one of the older proposals I've read. Personally, I always would like the validator to stop processing a given field on the first validation error. Does anyone see a need for any other behaviour than this? Does this really need to be configurable?
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 any of the six address fields don't validate, we assume the user opted to not provide the address.

If any one of the fields in the optional address are invalid (including because a required address field is missing), then the result from $outer->isValid() will still be true (assuming the other required fields validate). This is the consequence of making the inner validator optional. However, the validators of the inner validator will still raise their error messages, so you can check the error'ed fields and reason codes and decide what to display to the user. OK, so this probably isn't the best way to validate an address, but at least it shows how to nest Builders.

UC-05

This shows a better way to actually do the example given above. This uses another new validator I'd like to propose, Zend_Validate_AllOrNone. This allows you to do some limited conditional requirement of fields. You pass it a set of fields, and if all of them are empty, or if all of them are filled, then it considers the field valid. If only some are filled, then it considers the field invalid. This is great for situations like the optional address.

This way, you don't get error messages built up in your errors collection when the optional fields are omitted, simplifying your error handling code.

UC-06

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 do need it, this is invaluable. Also remember that you don't have to always use these two together as I have here.

UC-07

I used to build forms that mimiced my table structure, with the ability to edit multiple records for multiple tables in one form. In this example, assume we want to validate a CRUD form for a simple contact table, but the user can add multiple contacts on the form (maybe with the help of some Javascript like WForms). The form fields are laid out in a three-dimensional array, where the first key is the table name, the second key is the row number, and the third key is the field name. It would result in post data that looks something like this:

Assume that the user actually entered several contacts, so the 'contacts' array would have several sub-arrays, one for each record, just like a result set.

Here's how to set up a filter/validator that would process all of the records:

UC-O8

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-09

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.

UC-10

The fluent interface. This demonstrates the fluent Facade by re-writing UC-07. A lot of the "fluency" of the interface actually depends on the naming of the filter and validator classes.

UC-11

This is UC-08 re-written with the fluent Facade. In order to instantiate validator classes, the fluent Facade has to somehow prepend the "namespace" part of the class name to the "base name" of the class. This is a similar problem faced by the Zend_Filter_Input proposal, and is solved in a similar way. The fluent Facade for both Builder classes takes an options array to the constructor, and one of the option keys is appendNamespace. This option is an array of namespace prefixes that gets appended to the internal list of namespaces. Methods giving more control over this process are not available just yet.

UC-12

And finally, the big one, re-written with the fluent Facade. A bit less big now.

Namespaces and Class names
The factories used by the fluent Facade attempt to find classes by searching through an array of namespace prefixes. If you have filter or validator classes with the same "base name" in two different namespaces, only the first one found will be returned. There's no way to pick which namespace to use on a call-by-call basis. You can easily replace the factories with your own in order to implement different instantiation rules. The standard Builder classes are not affected by this problem.

9. Class Skeletons

Zend_Validate_Builder

Zend_Validate_Builder_FluentFacade

Zend_Validate_Builder_FluentAdder

Zend_Validate_Builder_ValidatorFactory

Zend_Validate_Builder_ValidatorFactory_Interface

Zend_Validate_Builder_ErrorManager

Zend_Validate_Builder_ErrorManager_Interface

Zend_Filter_Builder

Zend_Filter_Builder_FluentFacade

Zend_Filter_Builder_FluentAdder

Zend_Filter_Builder_FilterFactory

Zend_Filter_Builder_FilterFactory_Interface

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

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