Quantcast
Channel: Guide pour le framework Symfony
Viewing all articles
Browse latest Browse all 10

A sfValidator to convert a date from a given pattern to a SQL friendly pattern (Symfony 1.2)

$
0
0

Maybe I’ve just spend a few hours for nothing (if so, please let me be aware of that by commenting ;) ), but I wasn’t able to find a widget and/or a validator to do this bunch of things :

  • Display, in a form, a date select in a I18n manner (in my case, I need a french date display, that is to say dd/mm/yyyy
  • Display only a few years : I need only the current year and the year + 1
  • Having tomorrow’s date already selected in the form
  • Validate the date for today or past dates to be invalid
  • Last but not least, having this date clean to save in my MySQL database

In my form class, I create a widget sfWidgetFormDate(). But depending on how I display it in the template, some of the above options seem to be unreachable. I tried with :

//this is my default date, set to tomorrow
$default_date = date('Y-m-d', time() + 86400);

// the typical display
// in this one, I wasn't able to set the selected date
echo $form['date'];

// display using input_date_tag
// in this one, I wasn't able to set years. Even with sfWidgetFormDate(array('years' => array(date('Y'), date('Y') +1))), it wasn't working
echo input_date_tag('date', $default_date, array('culture' => 'fr_FR'));

// even by specifying years here : useless
echo input_date_tag('date', $default_date, array('culture' => 'fr_FR', 'years' => array(date('Y'), date('Y') + 1)));

// It was ok with the rich display, but then, I had issues : I was able to display the date with french pattern (dd/mm/yyyy), but after submitting the form, validation was not able to mix the date right to yyyy-mm-dd pattern which would eventually fit in a database...
echo input_date_tag('date', $default_date, array('culture' => 'fr', 'rich' => true, 'format' => 'dd/MM/yyyy'));

As the last one was really ergonomic for my purpose, I’ve decided to keep on this one. I only had to write down a validator which would get the french pattern date, check and clean it into a mysql-ready-to-save date.

Does such a validator already exists ? That’s my question, feel free to answer. But anyway, now that my trick is working, let me share it with you ! It doesn’t really validate ANY I18n date, but kinda a few :)

File : lib/sfValidatorDateI18n.class.php
/**
 * Extends sfValidatorDate
 * Convert a pattern date like d/m/Y to MySQL friendly Y-m-d or other pattern
 * @author Simon Hostelet
 *
 */
class sfValidatorDateI18n extends sfValidatorDate
{

	/**
	 * Configure the validator. Adding two more options to the regular validator
	 * input_date_format : date format from the form's input. Default is Y-m-d (ex: 2009-08-19)
	 * output_date_format : date format to get as output
	 * Allowed format masks:
	 * d : 01 to 31
	 * m : 01 to 12
	 * y : 00 to 99
	 * Y : example 2009
	 * Allowed format separators: /-_,. and space
	 * @see trunk/lib/vendor/symfony/lib/validator/sfValidatorDate#configure($options, $messages)
	 */
	protected function configure($options = array(), $messages = array())
  {

  	$this->addOption('input_date_format', 'Y-m-d');
  	$this->addOption('output_date_format', 'Y-m-d');

  	parent::configure($options, $messages);
  }

  /**
   * Override sfValidatorDate doClean. Quite strange : I had to copy/paste the original
   * code after my 'convertDateToFormat' method : a simple parent::doClean($value) would
   * not work !
   * @see trunk/lib/vendor/symfony/lib/validator/sfValidatorDate#doClean($value)
   */
	protected function doClean($value)
  {

  	$value = $this->convertDateToFormat($value);

// I had to copy/paste the rest of doClean, otherwise it wouldn't work ! I don't know why...
  	if (is_array($value))
    {
      $clean = $this->convertDateArrayToTimestamp($value);
    }
    else if ($regex = $this->getOption('date_format'))
    {
      if (!preg_match($regex, $value, $match))
      {
        throw new sfValidatorError($this, 'bad_format', array('value' => $value, 'date_format' => $this->getOption('date_format_error') ? $this->getOption('date_format_error') : $this->getOption('date_format')));
      }

      $clean = $this->convertDateArrayToTimestamp($match);
    }
    else if (!ctype_digit($value))
    {
      $clean = strtotime($value);
      if (false === $clean)
      {
        throw new sfValidatorError($this, 'invalid', array('value' => $value));
      }
    }
    else
    {
      $clean = (integer) $value;
    }

    if ($this->hasOption('max') && $clean > $this->getOption('max'))
    {
      throw new sfValidatorError($this, 'max', array('value' => $value, 'max' => date($this->getOption('date_format_range_error'), $this->getOption('max'))));
    }

    if ($this->hasOption('min') && $clean getOption('min'))
    {
      throw new sfValidatorError($this, 'min', array('value' => $value, 'min' => date($this->getOption('date_format_range_error'), $this->getOption('min'))));
    }

    return $clean === $this->getEmptyValue() ? $clean : date($this->getOption('with_time') ? $this->getOption('datetime_output') : $this->getOption('date_output'), $clean);
  }

  /**
   * converts the given date
   * $value must match the option 'input_date_format'. It will be convert to 'output_date_format'
   * For example : $value = 19/08/2009, input_date_format = DD/MM/YYYY, output_date_format = YYYY-MM-DD
   * Output will be 2009-08-19
   * @param $value
   * @return unknown_type
   * @author  Simon Hostelet
   */
  protected function convertDateToFormat($value)
  {

  	// we check if input/output_date_format are well written,
  	// we get the date separator, and the order of year, month and day in the mask
    $input_details = $this->getDateAlrightSeparatorAndOrder($this->getOption('input_date_format'));
    foreach($input_details as $key => $val)
    {
    	$key = 'input_' . $key;
    	$$key = $val;
    }
    $output_details = $this->getDateAlrightSeparatorAndOrder($this->getOption('output_date_format'));

    $input_date = explode($input_date_separator, $value);

    // is this date valid ?
    if(!checkdate(intval($input_date[$input_month_order]), intval($input_date[$input_day_order]), intval($input_date[$input_year_order])))
    {
    	throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }

    // let's build the output date
    $output_date = $this->getOption('output_date_format');
    $output_date = preg_replace('/Y|y/', $input_date[$input_year_order], $output_date);
    $output_date = preg_replace('/m/', $input_date[$input_month_order], $output_date);
    $output_date = preg_replace('/d/', $input_date[$input_day_order], $output_date);

    return $output_date;
  }

  /**
   * get a date format (like d/m/Y), check the format, and returns date separator (-/_, .)
   * and date order
   * @param $format
   * @return array
   * @author  Simon Hostelet
   */
  protected function getDateAlrightSeparatorAndOrder($format)
  {
    // does the date_format looks right ?
    if(!preg_match('/(d|m|y|Y)([-\/_,\. ]{1})(d|m|y|Y)([-\/_,\. ]{1})(d|m|y|Y)/', $format, $matches))
    {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }

  	// what is the date separator ?
    preg_match('/[dmyY]{1}([\/\-,\._ ]{1})[dmyY]{1}/', $format, $matches);
    $return_array['date_separator'] = $matches[1];

    // what is the order of day, month and year in the format mask ?
    $date_order = explode($return_array['date_separator'], $format);
    foreach($date_order as $key => $val)
    {
      switch($val)
      {
        case 'd':
          $return_array['day_order'] = $key;
          break;
        case 'm':
          $return_array['month_order'] = $key;
          break;
        default:
          $return_array['year_order'] = $key;
      }
    }

    return $return_array;
  }
}

In the Form class configure :

$this->validatorSchema['date'] = new sfValidatorDateI18n(array(
      'date_output' => 'Y-m-d',
      'input_date_format' => 'd/m/Y',
      'output_date_format' => 'Y-m-d',
      'with_time' => false,
    ));


Viewing all articles
Browse latest Browse all 10

Trending Articles