Asked  1 Year ago    Answers:  5   Viewed   11 times

I am slowly building up my Zend skills by building some utility websites for my own use. I have been using Zend Forms and Form validation and so far have been happy that I have been understanding the Zend way of doing things. However I am a bit confused with how to use Zend_Validate_Db_NoRecordExists() in the context of an edit form and a field that maps to database column that has to be unique.

For example using this simple table

TABLE Test
(
  ID INT AUTO_INCREMENT,
  Data INT UNIQUE
);

If I was simply adding a new row to the Table Test, I could add a validator to the Zend Form element for the Data field as such:

$data = new Zend_Form_Element_Text('Data');
$data->addValidator( new Zend_Validate_Db_NoRecordExists('Test', 'Data') )

At form validation this validator will check that the contents of the Data element does not already exist in the table. Thus the insert into Test can go ahead without violating the Data fields UNIQUE qualifier.

However the situation is different when editing an existing row of the Test table. In that case the validator needs to check that the element value meets one of two mutually exclusive conditions conditions:

  1. The user has changed the element value, and the new value does not currently exist in the table.

  2. The user has Not changed the element value. Thus the value does currently exist in the table (and this is OK).

The Zend Validation Docs talk about adding a parameter to the NoRecordExists() validator for the purpose of excluding records from the validation process. The idea being to "validate the table looking for any matching rows, but ignore any hits where the a field has this specific value". Such a use case is what is needed for the validating the element when editing a table. The pseudo code to do this in 1.9 is like so (actually I got this from the 1.9 source code - I think the current docs may be wrong):

$data = new Zend_Form_Element_Text('Data');
$data->addValidator( new Zend_Validate_Db_NoRecordExists('Test', 'Data',
                     array ('field'=>'Data', 'Value'=> $Value) );

The problem is that the value that is to be excluded ($Value) is bound to the validator at the time it is instantiated (also when the form is instantiated). But when the form is editing a record, that value needs to be bound to the contents of the $data field when the form was initially populated with data - IE the Data value initially read from the Test table row. But in typical Zend patterns a form is instantiated and populated in two separate steps which precludes binding the exclude value to the desired element value.

The following Zend psuedo code marks where I would like the binding of $Value to the NoRecordExists() validator to occur (and note that this is a common Zend controller pattern):

$form = new Form() 
if (is Post) {
    $formData = GetPostData()
    if ($form->isValid($formData)) {
        Update Table with $formData
        Redirect out of here
    } else {
        $form->populate($formData)
    }
} else {
    $RowData = Get Data from Table
    $form->populate($RowData)     <=== This is where I want ('value' => $Value) bound
}

I could sub-class Zend_Form and override the populate() method to do a one-shot insertion of the NoRecordExists() validator on initial form population, but that seems like a huge hack to me. So I wanted to know what other people think and is there some pattern already written down that solves this problem?

Edit 2009-02-04

I've been thinking that the only decent solution to this problem is to write a custom validator and forget about the Zend version. My form has the record ID as hidden field, so that given the table and column names I could craft some SQL to test for uniqueness and exclude the row with an ID of such an such. Of course this started me thinking about how I would be tying the form to the dB layer that the Model is supposed to hide!

 Answers

2

After reviewing the overwhelming response I've decided that I'm going with a custom validator

Thursday, April 1, 2021
 
edsk
 
5

when you point with your apache document root to C:/xampphtdocs/ZendSkeletonApplication/ZendSkeletonApplication-master/public

you need to use in your browser this url http://zf2-tutorial.localhost:8081/album and not like you wrote http://zf2-tutorial.localhost/ZendSkeletonApplication/ZendSkeletonApplication-master/public/album

this url points internal to a different module/location.

//edit

if this not work check your zf2 /public folder if there is a .htaccess file present otherwise use the file from the zend skeleton application here https://github.com/zendframework/ZendSkeletonApplication/blob/master/public/.htaccess

please check also your apache vhost entry if the port is equal to your windows host file port.

make sure apache ModRewrite is loaded!

Tuesday, July 27, 2021
 
2

When you call isValid on a Zend_Form it will pass the all the data you passed to the method

$form->isValid(array('a' => 1, 'b' => 2));

Your custom validator will receive that whole array of raw values.

Example Validator

class My_Validator_TwoVals implements Zend_Validate_Interface
{
    public function getMessages()
    {
        return array();
    }
    public function isValid($value)
    {
        print_r(func_get_args());
    }
}

Example Form

$f = new Zend_Form;
$a = $f->createElement('Text', 'a');
$b = $f->createElement('Text', 'b');
$b->addPrefixPath('My_Validator', '.', 'validate');
$b->addValidator('TwoVals');
$f->addElements(array($a, $b));

$f->isValid(array('a' => 1, 'b' => 2));

Output

Array
(
    [0] => 2
    [1] => Array
        (
            [a] => 1
            [b] => 2
        )
)

As you can see there was also a second argument passed to isValid, which is $context. And that contains the remaining values.

An alternative would be to pass the second element to match against as an option to the Validator, e.g.

class My_Validator_TwoVals implements Zend_Validate_Interface
{
    protected $a;
    public function getMessages()
    {
        return array();
    }
    public function isValid($value)
    {
        var_dump($this->a->getValue());
    }
    public function __construct(Zend_Form_Element $a)
    {
        $this->a = $a;
    }
}

Setup

$f = new Zend_Form;
$a = $f->createElement('Text', 'a');
$b = $f->createElement('Text', 'b');
$b->addPrefixPath('My_Validator', '.', 'validate');
$b->addValidator('TwoVals', false, array($a));
$f->addElements(array($a, $b));

$f->isValid(array('a' => 1, 'b' => 2));

Will then print int(1). As you can see, we fetched that value through the form element's API so anything you configured for validators and filters would be applied, e.g. it's not the raw value. And you can also set it to another value, etc.

Also have a look at Zend_Validate_Identical to learn how ZF implements checking of other form elements:

  • http://framework.zend.com/svn/framework/standard/trunk/library/Zend/Validate/Identical.php
Wednesday, November 17, 2021
 
3

Adding the Zend_Filter_HtmlEntities filter, I would avoid entirely. Instead, I'd worry about escaping html entities only when displaying the data back to the user.

Thursday, November 25, 2021
 
4

If you really need to use the setOrder() method, I'd work with order numbers 10, 20, 30, 40, ... This way it will be easy to add elements in between already set Elements.

Furthermore, in order to avoid using order-numbers twice, you could use an array, where you store all the numbers from 1 to X. Whenever you set an order number, you set it via a method called getOrderNumberFromArray() which returns the next higher or lower order number still available in the array and unsets this array element.

Alternatively, and maybe even better, you could do getOrder() on the element you want to have before the new element, then increment this order number by X and then loop through the existing form elements and check that the order number doesn't exist yet.

Or you could just use getOrder() on the Element you want to show before and after the new element and make sure you don't use the same order numbers for the new element.

Thursday, December 30, 2021
 
James
 
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :