Zend Form Validate Range Date
Asked Answered
D

2

6

who gives me a hand to create a custom validator for Zend Framework, which checks that a date is in to a range?

Example:

dateGT = 2011-09-05
dateLT = 2011-07-05

if the form field is set to: dateFieldForm = 2011-08-15 I expect the validator returns true!

and if the form field is set to: dateFieldForm = 2011-10-15 I expect the validator returns false!

Dragster answered 17/6, 2011 at 15:4 Comment(0)
P
12

Sorry, large code ahead:

<?php

/** @see Zend_Validate_Abstract */
require_once 'Zend/Validate/Abstract.php';

/**
 * @category   Zend
 * @package    Zend_Validate
 * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class My_Validate_DateCompare extends Zend_Validate_Abstract
{
    /**
     * Error codes
     * @const string
     */
    const NOT_SAME      = 'notSame';
    const MISSING_TOKEN = 'missingToken';
    const NOT_LATER     = 'notLater';
    const NOT_EARLIER   = 'notEarlier';
    const NOT_BETWEEN   = 'notBetween';

    /**
     * Error messages
     * @var array
     */
    protected $_messageTemplates = array(
        self::NOT_SAME       => "The date '%value%' does not match the required",
        self::NOT_BETWEEN    => "The date is not in the valid range",
        self::NOT_LATER      => "The date '%value%' is not later than the required",
        self::NOT_EARLIER    => "The date '%value%' is not earlier than required",
        self::MISSING_TOKEN  => 'No date was provided to match against',
    );

    /**
     * @var array
     */
    protected $_messageVariables = array(
        'token' => '_tokenString'
    );

    /**
     * Original token against which to validate
     * @var string
     */
    protected $_tokenString;
    protected $_token;
    protected $_compare;

    /**
     * Sets validator options
     *
     * @param  mixed $token
     * @param  mixed $compare
     * @return void
     */
    public function __construct($token = null, $compare = null)
    {
        if (null !== $token) {
            $this->setToken($token);
            $this->setCompare($compare);
        }
    }

    /**
     * Set token against which to compare
     *
     * @param  mixed $token
     * @return Zend_Validate_Identical
     */
    public function setToken($token)
    {
        $this->_tokenString = (string) $token;
        $this->_token       = $token;
        return $this;
    }

    /**
     * Retrieve token
     *
     * @return string
     */
    public function getToken()
    {
        return $this->_token;
    }

    /**
     * Set compare against which to compare
     *
     * @param  mixed $compare
     * @return Zend_Validate_Identical
     */
    public function setCompare($compare)
    {
        $this->_compareString = (string) $compare;
        $this->_compare       = $compare;
        return $this;
    }

    /**
     * Retrieve compare
     *
     * @return string
     */
    public function getCompare()
    {
        return $this->_compare;
    }

    /**
     * Defined by Zend_Validate_Interface
     *
     * Returns true if and only if a token has been set and the provided value
     * matches that token.
     *
     * @param  mixed $value
     * @return boolean
     */
    public function isValid($value)
    {
        $this->_setValue((string) $value);
        $token = $this->getToken();

        if ($token === null) {
            $this->_error(self::MISSING_TOKEN);
            return false;
        }

        $date1 = new Zend_Date($value);
        $date2 = new Zend_Date($token);

        // Not Later
        if ($this->getCompare() === true){
            if ($date1->compare($date2) < 0 || $date1->equals($date2)) {
                $this->_error(self::NOT_LATER);
                return false;
            }
        // Not Earlier
        } elseif ($this->getCompare() === false) {
            if ($date1->compare($date2) > 0 || $date1->equals($date2)) {
                $this->_error(self::NOT_EARLIER);
                return false;
            }
        // Exact Match
        } elseif ($this->getCompare() === null) {
            if (!$date1->equals($date2)) {
                $this->_error(self::NOT_SAME);
                return false;
            }
        // In Range
        } else {
            $date3 = new Zend_Date($this->getCompare());

            if ($date1->compare($date2) < 0 || $date1->compare($date3) > 0) {
                $this->_error(self::NOT_BETWEEN);
                return false;
            }
        }

        // Date is valid
        return true;
    }
}

Usage:

$element->addValidator(new My_Validate_DateCompare('startdate')); //exact match
$element->addValidator(new My_Validate_DateCompare('startdate', null)); //exact match
$element->addValidator(new My_Validate_DateCompare('startdate', 'enddate')); //between dates
$element->addValidator(new My_Validate_DateCompare('startdate', true)); //not later
$element->addValidator(new My_Validate_DateCompare('startdate', false)); //not earlier

Uses the globally set date format (stored in Zend_Registry('Locale')).

It is also advisable to customize the error messages per case.

Latest update: fixed wrong default optional parameter which should be NULL instead of True. Changed the messages to be less confusing. Some formatting and whitespace.

Procurator answered 17/6, 2011 at 15:5 Comment(9)
sorry but if I want to use the magic methos like I should call the validator? $this->bookingdate->addValidator('DateCompare', false, array($this->begindate, $this->enddate)); I've tried so it does not work!Dragster
you have to register the paths to your library $form->addElementPrefixPath('My_Foo_Validate', 'My/Foo/Validate', 'validate'); , you also could change the prefix of the class to match your libraryProcurator
I finnally solved it this way: in my controller set the form $form = new Form_SetBookingDateChecking(array('begindate' => $checking->begindate, 'enddate' => $checking->enddate)); into the form using setter and getter and I use validator so: $this->bookingdate->addValidator('DateCompare', false, array($this->getBegindate(), $this->getEnddate()));. I want to clarify that in your function there is a bug, this line: if ($date1->compare($date2)>0 || $date1->compare($date3)<0){ should be if ($date1->compare($date2) < 0 || $date1->compare($date3) > 0) {. thanks again!Dragster
lol it is possible, i will fix it here, i actually made this a long time ago but end up not using it.Procurator
great! please can you put my comment as great? :DDragster
I write a test phpunit for your class. I hope it can help you. I post in the next answers!Dragster
Hello, I've used this validator in a project and I have identified one bug and one possibly confusing use case: 1) the error messages contain the token (not the value) as the date; for example "The date '%token%' is not in the valid range" should be "The date '%value%' is not in the valid range"; on error, for example for verifying that mydate is between startdate and enddate, the current error message will be 'The date startdate is not in the valid range'.Jehu
2) the Zend_Date objects don't have a format specified; I've found that as a good practice, when creating the Zend_Date, to specify the format explicitly (especially in a reusable component); for example, I've modified the validator to accept a third parameter, the format, and all new Zend_Date call to use that format.Jehu
Thanks for the tips I made the messages clearer. The problem is that you could not put the passing arguments into the message template, so they should be overriden by the programmer per case. I also assumed that the date format is globally set. There could be a number of more improvements. I can think of a new case like "Out of the given range", which can be implemented with the same options, but the startdate is larger than the enddate. But all of that could be done by the developer.Procurator
D
2

I write a test phpunit for class by venimus. I hope it can help someone.

class Validate_DateCompareTest extends PHPUnit_Framework_TestCase {

public function dataForTest()
{
    return array(
        array('2011-06-05', '2011-06-01', '2011-06-10', true), //between dates
        array('2011-06-10', '2011-06-01', '2011-06-10', true), //between dates
        array('2011-06-01', '2011-06-01', '2011-06-10', true), //between dates
        array('2011-06-15', '2011-06-01', '2011-06-10', false), //between dates
        array('2011-05-30', '2011-06-01', '2011-06-10', false), //between dates
        array('2011-06-01', '2011-06-01', null, true), //exact match
        array('2011-06-15', '2011-06-01', null, false), //exact match
        array('2011-05-30', '2011-06-01', null, false), //exact match
        array('2011-06-02', '2011-06-01', true, true), //not later
        array('2011-06-01', '2011-06-01', true, false), //not later
        array('2011-05-30', '2011-06-01', true, false), //not later
        array('2011-05-30', '2011-06-01', false, true), //not earlier
        array('2011-06-01', '2011-06-01', false, false), //not earlier
        array('2011-06-10', '2011-06-01', false, false), //not earlier
    );
}

/**
 * @dataProvider dataForTest
 * @param $value
 * @param $token
 * @param $compare
 * @param $expected
 * @return void
 */
public function testDateCompare($value, $token, $compare, $expected)
{
    /** @var $validate My_Validate_DateCompare */
    $validate = new My_Validate_DateCompare($token, $compare);
    $this->assertEquals(
        $expected,
        $validate->isValid($value),
        "value: $value -- token: $token -- compare: $compare"
    );
}

}

this class has been really helpful to me thanks again!

Dragster answered 22/6, 2011 at 13:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.