Michaël Perrin

Sustainable web development.

Range date validator for Symfony2

Symfony2 provides a date validator and a range validator for integers but no range validator for dates.

We’re going to implement one in this post.

Implement the Constraint class

Acme/DemoBundle/Validator/Constraints/DateRange.php

<?php  
namespace Acme\DemoBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;  
use Symfony\Component\Validator\Exception\MissingOptionsException;

/**
 * @Annotation
 */
class DateRange extends Constraint  
{
    public $minMessage = 'This date should be greater than {{ limit }}.';
    public $maxMessage = 'This date should be less than {{ limit }}.';
    public $invalidMessage = 'This value should be a valid date.';
    public $min;
    public $max;

    public function __construct($options = null)
    {
        parent::__construct($options);

        if (null === $this->min && null === $this->max) {
            throw new MissingOptionsException('Either option "min" or "max" must be given for constraint ' . __CLASS__, array('min', 'max'));
        }

        if (null !== $this->min) {
            $this->min = new \DateTime($this->min);
        }

        if (null !== $this->max) {
            $this->max = new \DateTime($this->max);
        }
    }
}

Implement the Validator class

Acme/DemoBundle/Validator/Constraints/DateRangeValidator.php

<?php  
namespace Acme\DemoBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;  
use Symfony\Component\Validator\ConstraintValidator;

class DateRangeValidator extends ConstraintValidator  
{
    /**
     * {@inheritDoc}
     */
    public function validate($value, Constraint $constraint)
    {
        if (null === $value) {
            return;
        }

        if (!($value instanceof \DateTime)) {
            $this->context->addViolation($constraint->invalidMessage, array(
                '{{ value }}' => $value,
            ));

            return;
        }

        if (null !== $constraint->max && $value > $constraint->max) {
            $this->context->addViolation($constraint->maxMessage, array(
                '{{ value }}' => $value,
                '{{ limit }}' => $this->formatDate($constraint->max),
            ));
        }

        if (null !== $constraint->min && $value < $constraint->min) {
            $this->context->addViolation($constraint->minMessage, array(
                '{{ value }}' => $value,
                '{{ limit }}' => $this->formatDate($constraint->min),
            ));
        }
    }

    protected function formatDate($date)
    {
        $formatter = new \IntlDateFormatter(
            null,
            \IntlDateFormatter::SHORT,
            \IntlDateFormatter::NONE,
            date_default_timezone_get(),
            \IntlDateFormatter::GREGORIAN
        );

        return $this->processDate($formatter, $date);
    }

    /**
     * @param  \IntlDateFormatter $formatter
     * @param  \Datetime          $date
     * @return string
     */
    protected function processDate(\IntlDateFormatter $formatter, \Datetime $date)
    {
        return $formatter->format((int) $date->format('U'));
    }
}

Use it

The min and max attributes can be set to any value the PHP DateTime class can parse (see http://www.php.net/manual/en/datetime.formats.php)

If you use the YAML format for configuration file, here is an example of how to use the validator:

src/Acme/DemoBundle/Resources/config/validation.yml:

Acme\DemoBundle\Entity\AnEntity:  
    properties:
        my_date_field:
            - Date: ~
            - Acme\DemoBundle\Validator\Constraints\DateRange:
                min: "today"
                max: "2014-03-20"

Michaël Perrin

Read more posts by this author.

comments powered by Disqus