Skip to end of metadata
Go to start of metadata

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

<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.3 (wiki revision: 50)

Table of Contents

1. Overview

This proposal is simply an alternative to the Zend_Filter_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 was originally inspired by Christopher Thompson's Zend_FilterChain proposal, and provides a vaguely similar interface. This takes quite a different tact than the array-driven structure used by Zend_Filter_Input; some people may find the code-driven interface preferable.

Here's a quick rundown of the major parts:

  • Zend_Filter_Builder and Zend_Validate_Builder classes
  • Facades providing a fluent interface for both Builder classes
  • An Error Manager class to facilitate configuring error messages for many validators at once, as well as retrieving raised errors

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)
  • This solution is not designed to make it easier to use single filters or validators; rather, it automates using many filters/validators together on whole sets of data
  • This is compatible with the existing Zend_(Validate|Filter)_Interface API; i.e., existing Framework filters and validators will work without being modified

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, there are also 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. The Zend_Validate_Builder_ErrorManager class addresses this concern. It provides a way for the Zend_Validate_* classes, Zend_Validate_Builder, and your application code to define and communicate error messages. 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 must be done manually, on each individual instance with the built-in validators. 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 retrieve 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: [DONE] Gather feedback and revise design as necessary (done, but more feedback is always welcomed)
  • Milestone 4: [DONE] Develop full implementation and unit tests
  • Milestone 5: Write documentation (this proposal actually is the current 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 class 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; variables such as $post pretty obviously refer to the user-submitted data. In most of these examples, I refer to Zend_Validate_* classes that don't exist in the Framework. Many of them are available in the Laboratory, along side ZVB and ZFB; as for the others--just assume they exist for now.

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 add method takes an optional third argument, which are flags for that specific combination of field and validator. 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. All the validators assigned to a field will always be executed. Therefore, a single field might generate several error messages, depending on how many validators it fails. Since these error messages are built up in the Error Manager object, your application code can decide which errors to display to the user; you need not display all of them.

UC-02

This demonstrates a little more advanced usage.

Pattern matching:

Specifying the PATTERN flag causes the Builder classes to interpret the field name argument as a PCRE regex to be matched against input field names. The given validator or filter is applied to each matching field.

Validating each field in a list:

Note
Processing arrays of patterns (i.e., array in 2nd parm, with PATTERN flag in 3rd) is currently not supported. However, it is possible (and likely) to be added in the future.
UC-03

Since this follows the Composite pattern, you can nest validators to do some neat things. In this example, assume we're trying to validate a mailing address. Suppose, for organizational purposes, you structured the form so the mailing address was in a sub-array, something like this:

You could validate this nested structure like so:

If you have a situation where the entire address is optional, you might be temped to pass the OPTIONAL flag to the address field. However, that won't work: when the OPTIONAL flag is set, the Builder checks the value of the current field, and if it's blank, the Builder never calls the validator. Here, the current field would be a six-element array, which will never be considered empty. The elements might be empty, but the array itself won't be. Therefore, the entire address field will always be validated against the sub-validator. In this situation, what you actually need is conditionally optional fields, which requires completely different logic.

UC-O4

Password confirmation, catpcha validation, etc. This uses the PASS_GROUP flag, and a pair of similar validators called Zend_Validate_Equals and Zend_Validate_AllEqual. Zend_Validate_Equals takes a scalar value and compares it to a standard value passed to the constructor. Zend_Validate_AllEqual takes an array, and returns true if all of its elements equal each other. Both of these can be found in the Laboratory, and both optionally support strict type checking, which is false by default. The PASS_GROUP flag allows you to tell the Builder to pass all the fields named in the second parameter as a single array to the validator. This is handy for validators like Zend_Validate_AllEqual, which examine the contents of several fields in the input to decide if they are valid.

Password Confirmation:

Catpcha validation:

UC-05

In order to handle a situation like the optional address mentioned above, you'd need something like this. This uses another new validator called Zend_Validate_AllOrNone. It allows you to make some fields conditionally required. You pass it a set of fields, and will consider them valid under two distinct cases: if either all of them are empty, or if all of them are filled. If only some are filled, then it considers the set invalid. To get the most benefit from this, combine it with other validators. The example may clarify things a bit. This validator can be found in the Laboratory, with the Builder classes.

Here, the OPTIONAL flag is passed to all the validators after the AllOrNone validator. This is so those validators don't fail the field in the case when the field is omitted (the fields are still opitonal; albeit conditionally optional).

UC-06

Zend_Validate_Builder and Zend_Filter_Builder really promote composing together validators to do interesting things. Here are some examples that show some of neat things that can be done with custom "Decorator" validators. Keep in mind that a Zend_Validate_Builder instance counts as a validator that can be decorated; if you're creative, you can probably come up with some really cool things to do with this.

Zend_Validate_Array
Most validators only take a scalar value to isValid. Zend_Validate_Array turns an exisiting scalar validator into an array validator. You pass a validator to the constructor, and an array value to isValid. It will then 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_AtLeastN and Zend_Validate_AtMostN
These two are decorating array validators. They expect an array passed to isValid, a validator instance passed to the constructor, and check that at least (or at most) n elements in the array pass the validator. This works great with the PASS_GROUP flag when your input data doesn't already have an array in it.

I haven't come up with a good, realistic example for this, but there's a lot of creative potential here. Also remember that you don't have to always use these two together as I have here. All three of these validators are in the Laboratory.

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 (with the help of some Javascript in UI). 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 DB query result set.

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

UC-08

So where have the error messages from all these validators been going all this time? To the Error Manager! This shows how to use the Error Manager both to retrieve raised errors, and define custom error messages.

The Zend_Validate_Interface interface defines the method getMessages, which returns an array of all the error messages raised by the validator, indexed by error code. Zend_Validate_Builder implements this method (albeit, with an incompatible return value), which you can use to retrieve all the error messages that have been raised during the entire validation run. Internally, it delegates this work to the Error Manager; so for retrieving messages, you don't need to touch it directly. You only need to access the Error Manager directly when you want to use it set up custom error messages, or want more control over which errors are returned.

Retreiving errors
Normally, calling getMessages on a validator returns a flat array of error messages. But because the Builder manages a whole collection of fields and validators, it's return value is slightly different. It returns a three-dimensional array, indexed by field name, validator class name, and failure reason code. Say the user entered a value in the name field that was too short:

Of course, if there were other fields and validators that failed, their messages would all be in the returned array, under the appropriate index.

Custom error messages
You can grab an instance of the Error Manager object to set any number of custom error messages for any combination of field, validator, and failure reason code.

The Error Manager provides a hasMessages method, in addition to getMessages. Both of these methods take optional arguments that allow you to filter down to a particular subset of the error messages you might be interested in.

One key benefit of all this is that it allows you to completely separate your error messages from the code that does validation. You could put all your messages in a separate file, or use a Zend_Config object to load all the error messages for a form at once. Among other things, this should make I18N much easier.

Here's one possible way to store errors in an INI file, and set them via Zend_Config. The setTemplates method makes this possible, since it accepts an entire array structure of error messages.

errors.ini

SomeClass.php

UC-09

This use case is 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).

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.

I've omitted all but the validate() method for the sake of clarity.

UC-10

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

UC-11

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

Get the Code!
The full implementation of these classes can be checked out from the Zend Laboratory SVN repository: http://framework.zend.com/svn/laboratory. The source code is in the library/Zend/Filter and library/Zend/Validate directories. The unit tests are in tests/Zend/Filter and tests/Zend/Validate.

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

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

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

    <p>First of all Bryce thank you for working on this. It's very interesting and I think it could be a good solution. I did put some work into my solution, but if it is scrapped in favor of a better solution I'm okay with that, and I'll know that my work served as a step towards the best solution.</p>

    <p>Some initial thoughts: </p>

    <p>The fluent interface and the builder patterns are nice. My solution uses a declarative syntax, which I also like, but your interface is more object-oriented and it supports IDE type tools somewhat better.</p>

    <p>Your design above makes developers call require_once on every Filter or Validate class that they use. One of the primary objections I saw to the "atomic" nature of having all individual filters and validators is that people don't like the three-step process: 1. require_once, 2. new <class>, 3. calling the call method. </p>

    <p>I suppose you could borrow my solution in yours, that is to allow developers to specify a filter or validator as a string as well as an object. Then map the string to a classname within the Zend class namespaces, or in user-specified alternative namespaces. Thus it can load the class and create a new object automatically. It's also easy in your interface that one could specify both the validator class and its constructor args easily:</p>
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    $validate->addValidator('description', array('StringLength', 255));
    ]]></ac:plain-text-body></ac:macro>

    <p>Another feature that is missing from your solution, my solution, and the current set of classes is the ability for the developer to specify validator error messages. This might be outside the scope of your proposal, so I will post a new proposal and get the ball rolling on that feature.</p>

    1. May 13, 2007

      <p>Ok, I have begun to write up a proposed solution for the error messages requirement.
      <a class="external-link" href="http://framework.zend.com/wiki/x/kHM">http://framework.zend.com/wiki/x/kHM</a></p>

  2. May 16, 2007

    <p>Great, Bryce. I really like your proposal but there is this one little thing I find disturbing:</p>

    <blockquote><p>"The most drastic change to the existing Zend_Validate_* classes that I propose here, is to make them all consider omitted fields valid."</p></blockquote>

    <p>I am strongly opposing to this. This means that if you want to use one validator for one value only , you need to instantiate two validators or use some kind of special case 'if' logic for an empty value in your controllers. This introduces a hidden and unexpected feature to validators and a change for existing applications.</p>

    <p>I see a need to omit empty in form processing, yes. But it's not the only case when you use validators. You may wish to validate incoming soap request/response, csv data or anything else where such handling of empty values is just not acceptable.</p>

    <p>What I would propose to solve this, is to use some kind of a flag for the builders to tell them how to handle empty elements. Like:</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[$validator = new Zend_Validate_Builder(Zend_Validate_Builder::SKIP_EMPTY_ELEMENTS)]]></ac:plain-text-body></ac:macro>

    1. May 16, 2007

      <p>I see what your saying. I actually didn't consider processing SOAP or CSV, etc. Honestly, I would rather like not to make such a drastic, non-backward compatible change to the existing validators, if there's a better way to acheive the goal.</p>

      <p>The reason I came up with that is because, in any given form, there may several required fields and several optional fields. Adding a specific validator to a field that makes it required ensures that even if that field is left empty, it gets caught. At the same time, other fields aren't forced to be required.</p>

      <p>How about this: Change the signature of <code>addValidator()</code>, so the third paramter is a more generic flags specification, then allow usage like so:</p>
      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      $validator->addValidator('my_field', new Zend_Validate_Between(5, 10), Zend_Validate_Builder::OPTIONAL);
      ]]></ac:plain-text-body></ac:macro>

      <p>Assuming the optional flag would be off by default, and the existing validators retained their current behaviour, this would result in most fields being required by default.</p>

      <p>The fluent Facade could theoretically provide special methods for setting the flags, but that would effectively reserve those names so that no validators could ever be added with those names (if they wanted to be accessible through the fluent Facade).</p>
      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      $validator->my_field->between(5, 10)->optional();
      ]]></ac:plain-text-body></ac:macro>

      <p>If this sounds good, I'll update the proposal.</p>

      1. May 16, 2007

        <p>Fine with me, Bryce. My "solution" was just an example, anyways. </p>

        <p>But, what could may not have been clear, my way allows you to use both of the worlds without any bigger changes to interfaces. You can leave the option at a default, which would mean validators would behave like they always behaved; or you could turn the "skip" option at a builder level to skip all empty elements but those which are supplied a Zend_Validate_Required making them, in fact, required. </p>

        <p>The effect is exactly the same, so whatever is more convenient for you. Thanks.</p>

        1. May 16, 2007

          <p>Oh, OK. I see what you're saying now, thanks for clearing that up. <ac:emoticon ac:name="smile" /> That's a good idea, and a lot easier to implement. I'll go ahead and update the proposal to add this kind of feature.</p>

          1. May 16, 2007

            <p>This'll teach me to think before posting... When I went to add the code for this, I realized that the builder-level "skip" option, no matter what form it takes, will never allow you to decide which fields are required/optional on a field-by-field basis, which is what I was alluding to in my first reply.</p>

            <p>The problem is that if a Validator is supposed to make the decision about whether a field is required, then the Builder <strong>has</strong> to pass the value to the Validator, right? But with a Builder-level skip option, the Builder will always skip the empty fields, and the Validators will never see them. I'm not willing to add some kind of class-name check to the code (that would create an unnecessary dependency), so the next best option seems to have a field-by-field flag that specifies whether or not it's required.</p>

            <p>Thoughts?</p>

            1. May 18, 2007

              <p>I was thinking about this today and came up with the following solution.</p>
              <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
              public function requiredFields($fields = array())
              {
              $this->_requiredFields = $fields;
              }

              function isValid()
              {
              $valid = true;
              foreach($this->_requiredFields as $field)
              {
              $value = $this->_dataSet[$field];
              if ($value == '')

              Unknown macro: { call_user_func($this->_errorCallback, 'ZEND_VALIDATE_BUILDER_REQUIRED', $field, $value); $valid = false; }

              }
              foreach ($this->_validator as $field => $validator_list)
              {
              $value = $this->_dataSet[$field];
              if ($value != '')
              {
              foreach ($validator_list as $validator)
              {
              if ($validator->isValid($value, $field))

              Unknown macro: { $this->_data[$field] = $value; }

              else

              Unknown macro: { $valid = false; }

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

              <p>To sum it up, required fields are declared using the "requiredFields" method. These fields are checked prior to traversing through all the validators with the Zend_Validate_Builder triggering the error callback. The validator rules, on the other hand, are only run on fields that contain input, otherwise they are skipped.</p>

              <p>This approach solves the need for Zend_Validate_* to return true on empty strings.</p>

              <p>thoughts? opinions?</p>

              1. May 19, 2007

                <p>With regard to needing the validators to pass empty strings, I beleive that requirement is no longer necessary with the current version of the propoasl, as it stands. As I mentioned on the mailing list, I thought more about it, and realized that just having the OPTIONAL flag did in fact work as expected. As soon as I can, I'm actually going to start writing some unit tests for this stuff so I have more than just speculation to back my words up. <ac:emoticon ac:name="smile" /></p>

                <p>Separately specifying the required fields is a pretty good idea on its own, though. Of course, I'd rather not change the API any more than I have to, but I can see how it would be nice to have an explicit, specific error raised when a required field is missing (as opposed to "'' is not an integer", for example).</p>

  3. May 16, 2007

    <p>Great.I like your proposal very much.It is much better if it can work with Zend_Config component.</p>

    1. May 16, 2007

      <p>It already works with Zend_Config, in a manner of speaking. Zend_Validate_Builder takes an options array as the second argument to the constructor, which can easily be set via Zend_Config:</p>
      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      $config = new Zend_Config_Ini('path/to/file.ini');
      $validate = new Zend_Validate_Buidler(null, $config->asArray());
      ]]></ac:plain-text-body></ac:macro>

      <p>If you're talking about adding validation rules via Zend_Config, that's a little different. It's actually a pretty good idea. It would be relatively straight-forward to build another Facade on top of Zend_Validate_Builder whose job was just to take a Zend_Config object and add a bunch of validators with <code>addValidator()</code>.</p>

      <p>If enough people chime in and say they want this feature, we could add it to the proposal.</p>

  4. May 17, 2007

    <p><em>If you're talking about adding validation rules via Zend_Config, that's a little different. It's actually a pretty good idea. It would be relatively straight-forward to build another Facade on top of Zend_Validate_Builder whose job was just to take a Zend_Config object and add a bunch of validators with addValidator().</em></p>

    <p>I'd love to see it - but I don't want to throw in too many requirements until the basic interface has settled in.</p>

    <p>Other than that my only comment would be the error handling. I've commented over on Bill's error messages proposal so no point repeating it here. I suppose the guts of my argument is that I don't want the controller defining and manipulating errors. I would prefer if the Controller treated the main Validation object more like a Model, querying for error messages which which were defined at the setup stage. Most of my error messages are going to be accompanying the Validation rules in a config file (likely XML) so everything is centralised. They should all be using the sprintf style placeholder syntax so Rule values/params can be integrated into them (or their translations) easily.</p>

    1. May 17, 2007

      <p>I think we're both in agreement about error messages being in the Controller – I want them to live somewhere else, as well.</p>

      <p>I currently use my own ErrorCollection class, whose error messages have <code>sprintf</code> paramters in them, so I'm definitely with you on that point.</p>

      <p>Have another look at the <code>handleError()</code> method in UC-07. In the comment above that message, I touch on what could be with it. You can't tell it from that method, but the <code>$this->errors->raise()</code> method takes a third argument, which is an array of values to be passed through <code>sprintf</code> into the error message.</p>

      <p>You can see how you can have your own error manangement class that does a similar thing: read in th error messages from a DB table, a config file (even the same one the validators are defined in), etc. Anything you want that can map a field name and reason code to an error message will work. And you also get the field value, as well (which may theoretically be an array, depending on the validator).</p>

      1. May 19, 2007

        <p>UC-07 is now UC-08.</p>

  5. May 21, 2007

    <p>A couple of comments:</p>

    <p>1.) Adding "field" to the Zend_Validate_? isValid interface is making less sense to me now than when I originally read the proposal. When you think about what the purpose of the Zend_Validate_? class you realize that "field" does not make sense in the context of if a value is valid or not. The only purpose of "field" is for the error callback. Why don't we move that responsibility to the Zend_Validate_Builder? All the Zend_Validate_? classes need to do is provide the reason(s) code for the failure. The Zend_Validate_Builder should be able to take that error code, the field, and the value and call the corresponding error callback.</p>

    <p>2.) Another problem that I see with this current implementation is that Zend_Validate_Builder has artifically imposed that the Zend_Validate_? classes all receive a single scalar value to validate. I'd like to propose a change so that the following call:<br />
    addValidator(array('name', 'address', 'city', 'state', 'zip'), Zend_Validate_AllOrNone());</p>

    <p>would send an array of input comprised of (name, address, city, state, zip) to the validator Zend_Validate_AllOrNone as opposed to the current implementation which would add the Zend_Validate_AllOrNone to the validator chain for all the fields specified.</p>

    <p>The benifit of such a change would allow developers to build custom Zend_Validate_? classes that could perform very complex form validation.</p>

    <p>thoughts?</p>

    1. May 22, 2007

      <p>About point #2, Zend_Validate_Builder doesn't actually force the validators to receive scalars; if the value of a field is an array, it will just pass the array as-is to the validator (see the proposed Zend_Validate_Array class). Ironically, when I first thought up this thing, I wanted the grouping functionality to pass the entire group as an array to the validator, just as you described here. But at that time, I couldn't really figure out how to implement that and the other stuff, like globbing and nested validators, without it getting really complicated. So I opted to simplify it.</p>

      <p>As for point #1, I conceptually agree with you; the field name parameter has little use outside of the Builder. Right now the code for Zend_Validate_Builder is quite simple, but making the Builder take over dealing with all of the errors will obviously make it quite a bit more complicated. So, I'm not really against making this change, but it's a lot of work to add.</p>

      <p>These are good ideas. I don't have time at the moment to analyze it any further (I still haven't put together unit tests), but hopefully I will in the next few days. Of course, I'd always welcome seeing some code. <ac:emoticon ac:name="smile" /></p>

  6. May 24, 2007

    <ac:macro ac:name="note"><ac:parameter ac:name="title">Zend Comment</ac:parameter><ac:rich-text-body>
    <p>This proposal is a powerful solution for many use cases regarding running multiple filters and validators against multiple inputs. However, it still has some maturation to go, and the bottom line is that the proposal came in too close to our deadline to make it into Zend Framework 1.0.</p>

    <p>We would like to offer you space in the Zend Framework Laboratory to continue exploring this solution. Specific comments we have are:</p>

    <ul class="alternate">
    <li>Some of the individual filters and validators you mention tangentially in this proposal are interesting and could be useful independently of the Builder proposal.</li>
    <li>On the other hand, some other validators proposed (e.g. Zend_Validate_Switch) have too much coupling between the class and the data representation.</li>
    <li>Altering the method signature for <code>isValid()</code> to accept a field name creates coupling between the class and the data representation.</li>
    <li>The user requirement to be able to instantiate filters/validators without use of <code>require_once</code> and <code>new</code> for each class has not been satisfied in the Builder class. The FluentFacade class does this, but the usage is confusing and feels like a duplicate solution to the same problem (even though it utilizes the Builder class).</li>
    <li>The user requirement to store filter/validation rules as data has not been satisfied in this proposal. If one stores filter/validation rules in a config file, one needs to implement an additional solution (a "Builder Builder") to convert config data into a validation Builder class.</li>
    <li>The usage of error callbacks is not clear, and also this solution requires a developer to write more code. Our feedback suggests that people want to write less code.</li>
    <li>Overall, this solution is very powerful and follows object-oriented principles and design patterns faithfully – but it does so to its own detriment. Simple scenarios require too much application code to set up. The proposed design therefore doesn't satisfy the Zend Framework goal of "extreme simplicity". This goal of ZF to be simple is a higher priority than the goal of OOP purity.</li>
    </ul>
    </ac:rich-text-body></ac:macro>

    1. Aug 07, 2007

      <p>This proposal is now updated to its second version. I believe that the majority of issues raised above have been dealt with, particularly with respect to API compatibility.</p>

      <p>With regard to "The user requirement to store filter/validation rules as data", I have made the design decision in this code to deliberately push that off to user-land code. The reasoning is that the Builder-style API makes creating such a solution very straight-forward, and frees this code up to concentrate on its core functionality.</p>

      <p>Also, this version of the proposal includes Factory classes that may be used to instantiate Filters and Validators without having to <code>require_once</code>/<code>new</code> them individually. Once you have an instance of a Factory, you can easily create many filters and validators. These Factories are independent of the Builder classes, so they may be used in a variety of situations. Also, they may easily replaced by the user.</p>

  7. May 30, 2007

    <p>What about To add an addValidators() method which can take a config array as an argument :</p>

    <p>$validators = arrays ( 'phone_no' => 'Numeric',<br />
    'phone_no' => array('Between',false,1000000, 9999999)),<br />
    'description' => array('StringLength', true, 255)<br />
    );</p>

    <p>$validator = new Zend_Validate_Builder; <br />
    $validator->addValidators($validators); <br />
    if ($validator->isValid($post)){ <br />
    // do your stuff<br />
    } </p>

    <p>I'm more in favour of an error object instead of a callback function.<br />
    Otherwise I like your proposal.</p>

  8. Oct 03, 2007

    <p>I like your <strong>second revision</strong> specially the Error Manager who makes live really easier on i18n issues.</p>

    <p>I have a couple of suggestion though. But well, you probably not gona like them as they break OO purity <ac:emoticon ac:name="smile" />. But it's why I like ZF, it's is good trade-off between OO and Simplicity.</p>

    <p><strong>Error Manager</strong></p>
    <ul>
    <li>Is it possible to cut down the method signature of Zend_Validate_Builder_ErrorManager setMessage() to setMessage('name', 'Zend_Validate_StringLength::TOO_SHORT', 'Name must be less than %min% characters long.') instead of setMessage('name', 'Zend_Validate_StringLength', 'stringLengthTooShort', 'Name must be less than %min% characters long.')</li>
    </ul>

    <ul>
    <li>Zend_Validate_Builder_ErrorManager::getMessages() and Zend_Validate_Builder_ErrorManager::getErrors() are confusing regards to the same methods in Zend_Validate_Interface may be Zend_Validate_Builder_ErrorManager::getMessageTemplates() or Zend_Validate_Builder_ErrorManager::getRawMessages() would be more appropriate and lese confusing.</li>
    </ul>

    <ul>
    <li>For the shake of simplicity It would be good to have the possibility to add a error message with Zend_Validate_Builder ::addValidator. Separation of concern between the view end logic is a best practice but this kind of component is a bit in-between the two world.</li>
    </ul>

    <ul>
    <li>I first thought that your class could be totally Locale aware but it's probably better to left it to Zend_Validate_*</li>
    </ul>

    <p><strong>Nested structure</strong><br />
    For validation of nested structure instead of inner and outer would it be possible to do something like:</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    $validator->addValidator(array('address/addr1', ' address /city'), $string100);
    $validator->addValidator(array(''address/addr2', ''address/addr3'), $string100, Zend_Validate_Builder::OPTIONAL);
    $validator->addValidator(''address/state', new Zend_Validate_InArray($states));
    ]]></ac:plain-text-body></ac:macro>

    <p><strong>About require fields</strong></p>

    <p>It seams to me that in a form, most of the time, there is more optional fields than required one. I suggest that the default behaviour of Zend_Validate_builder be all fields are optional or best as suggest by Michael Minicki the default behaviour could be defined by an option argument in the constructor.</p>

    <p><strong>Factory</strong><br />
    I don't like the factory to instantiate filters/validators it's to much tapping and hard to read. I really prefer to specify a validator as a string as in other framework's components. </p>

    <p><strong>Config</strong><br />
    It is probably easy to implement, but providing it, give a standard way of doing thing which I think is one of the goal of the framework.</p>

    <p>My $0.0001</p>

    1. Oct 05, 2007

      <p>Thank you for the comments!</p>

      <p>About your ErrorManager points, in order:</p>
      <ul>
      <li>Yes, this can be shortened. This is a good idea, thank you! It would also cause all the methods that took the 3 filter parameters to be changed.</li>
      <li>I don't really like the Zend_Validate_Interface for getErrors() and getMessages(), so I did it this way because it makes more sense to me. But I'd be willing to change it; I'm not attached.</li>
      <li>If I did add a parameter to ZVB::addValidator() for an error message, it would have to be the 4th parameter; it already uses 3. Since the 1st parameter can be an array or a pattern string to match multiple fields, dealing with the error message parameter starts to get messy. Do you pass arrays to the 4 parameter, just duplicate the single message, or what? How about patterns matching variable fields? It becomes much less simple that it first appears, even for the user (let alone implementation).</li>
      <li>One of the main points of this class is to help localization, so I'm not quite sure what you mean. I think if it's left to anything, it should be the application, not Zend_Validate_<strong>. This class is trying to relieve Zend_Validate_</strong> of that responsibility (one that I don't believe it should have in the first place).</li>
      </ul>

      <p>Nested structures:<br />
      Using sort-of "paths" like that is an interesting idea. I can't really see how it could be implemented right now, though. I could very possibly just not have enough brains for that. <ac:emoticon ac:name="smile" /> But I'll consider it. I don't know if you had any time to look at the actual implementation, but it's really very simple. There's not much code to it, but that very simplicity makes it hard to do stuff like this path thing, since the moving parts really don't have the right info available at the right times.</p>

      <p>One of the things I <strong>really</strong> wanted to do was to provide a flag (3rd parm) so that the array passed to the first parm of addValidator() was passed as a whole to the given validator. Then you could do this:</p>
      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      $zvb->addValidator(array('password1', 'password2'), new Zend_Validate_Equals);
      // Assume Zend_Validate_Equals takes an array and returns true if all the elements are equal
      ]]></ac:plain-text-body></ac:macro>
      <p>That would drastically reduce or eliminate the need for nested Builders in the first place. And it would really convenient. However, implementing that just isn't feasible with the current simple architecture I used (it could very well triple the code size).</p>

      <p>Required Fields:<br />
      Most of the forms I build have more required than optional fields. <ac:emoticon ac:name="smile" /> But adding a configure option for this would not be too hard. Another good idea!</p>

      <p>Factory:<br />
      I don't like typing long names any more than anyone else, that's why I added the Fluent Facade. It hides the Factory and other stuff behind a nice, short, easy to remember, easy to type API. You effectively get to specify the names as strings that way. It would be fairly easy to change addValidator() to take a string in the 2nd arg, and run it through the Factory, but then you loose the nice type hinting, and given the Fluent Facade, doesn't really seem all that necessary. It's an idea worth considering, though.</p>

      <p>Config:<br />
      It pretty much is already built in; that's what ErrorManager:setMessages() (note the plural) is for. You can do a toArray() on a config object straight into this. It's two lines of code, and in the doc comment as an example. I guess a convenience method could be added to take a config object and run the two lines of code...</p>

      <p>Thank you again for the input! I'll probably update the API and this proposal in the near future to incorporate some of these ideas.</p>

      1. Oct 05, 2007

        <p>Note that I forgot to escape the asterisks in the randomly bold part of the line above... <ac:emoticon ac:name="smile" /></p>