Code snippets for symfony 1.x

Navigation

Snippets by user yitznewton

Validator for latitude / longitude

<?php
/**
 * This validator accepts either a four-member array such as
 * array( 'deg' => 12, 'min' => 34, 'sec' => 56, 'dir' => 'N' )
 * or a numeric value such as 12.3456
 */
class ynValidatorLatLong extends sfValidatorBase
{
  protected function configure($options = array(), $messages = array())
  {
    parent::configure( $options, $messages );
 
    $this->addRequiredOption('axis');  // 'latitude' || 'longitude'
 
    $this->addMessage('invalid_axis', 'The selected axis is invalid.');
  }
 
  protected function doClean( $value )
  {
    $max = 0;
 
    switch ( $this->getOption('axis') ) {
      case 'latitude':
        $max = 90;
        break;
 
      case 'longitude':
        $max = 180;
        break;
 
      default:
        throw new sfValidatorError($this, 'invalid_axis');
    }
 
    if ( is_array( $value ) ) {
      $value = $this->arrayToInt( $value );
    }
 
    if ( ! is_numeric( $value ) ) {
      throw new sfValidatorError($this, 'invalid');
    }
 
    if ( abs( $value ) > $max ) {
      throw new sfValidatorError($this, 'invalid');
    }
 
    return $value;
  }
 
  protected function arrayToInt( array $value )
  {
    if ( count( $value ) != 4 ) {
      throw new sfValidatorError($this, 'invalid');
    }
 
    if (
      ! isset( $value['deg'] )
      || ! isset( $value['min'] )
      || ! isset( $value['sec'] )
      || ! isset( $value['dir'] )
    ) {
      throw new sfValidatorError($this, 'invalid');
    }
 
    if ( preg_match( '/\D/', $value['deg'].$value['min'].$value['sec'] ) ) {
      // non-integer component
      throw new sfValidatorError($this, 'invalid');
    }
 
    $multiplier = 1;
 
    switch ( strtolower( $value['dir'] ) ) {
      case 'n':
        $axis = 'latitude';
        break;
 
      case 's':
        $axis = 'latitude';
        $multiplier = -1;
        break;
 
      case 'e':
        $axis = 'longitude';
        break;
 
      case 'w':
        $axis = 'longitude';
        $multiplier = -1;
        break;
 
      default:
        throw new sfValidatorError($this, 'invalid');
    }
 
    if ( $axis != $this->getOption('axis') ) {
      throw new sfValidatorError($this, 'invalid');
    }
 
    return ( $value['deg'] + $value['min']/60 + $value['sec']/60/60 ) * $multiplier;
  }
}
 
/*
 * UNIT TEST
 */
 
<?php
require_once dirname(__FILE__).'/../../bootstrap/unit.php';
 
$t = new lime_test();
 
$lat_validator = new ynValidatorLatLong( array( 'axis' => 'latitude' ) );
$lon_validator = new ynValidatorLatLong( array( 'axis' => 'longitude' ) );
 
$lat_good = array(
  array( 'deg' => 50, 'min' => 12, 'sec' => 14, 'dir' => 'N' ),
  array( 'deg' => 50, 'min' => 12, 'sec' => 14, 'dir' => 'S' ),
  50.456789,
  50,
  -50.456789,
  -50
);
 
$lat_bad = array(
  'three members' => array( 'deg' => 50, 'min' => 12, 'sec' => 14 ),
  'invalid n/s 1' => array( 'deg' => 50, 'min' => 12, 'sec' => 14, 'dir' => 'F' ),
  'invalid n/s 2' => array( 'deg' => 50, 'min' => 12, 'sec' => 14, 'dir' => 'E' ),
  'non-integer member' => array( 'deg' => 50, 'min' => 12, 'sec' => 14.2, 'dir' => 'N' ),
  'out of range' => array( 'deg' => 100, 'min' => 12, 'sec' => 14, 'dir' => 'N' ),
);
 
$lon_good = array(
  array( 'deg' => 150, 'min' => 12, 'sec' => 14, 'dir' => 'E' ),
  array( 'deg' => 150, 'min' => 12, 'sec' => 14, 'dir' => 'W' ),
  50.456789,
  50,
  -50.456789,
  -50
);
 
$lon_bad = array(
  'three members' => array( 'deg' => 150, 'min' => 12, 'sec' => 14 ),
  'invalid e/w 1' => array( 'deg' => 150, 'min' => 12, 'sec' => 14, 'dir' => 'F' ),
  'invalid e/w 2' => array( 'deg' => 150, 'min' => 12, 'sec' => 14, 'dir' => 'N' ),
  'non-integer member' => array( 'deg' => 150, 'min' => 12, 'sec' => 14.2, 'dir' => 'E' ),
  'out of range' => array( 'deg' => 200, 'min' => 12, 'sec' => 14, 'dir' => 'E' ),
);
 
foreach ( $lat_good as $value ) {
  try {
    $clean = $lat_validator->clean( $value );
    $t->ok( is_numeric( $clean ), 'is numeric' );
 
    if ( is_array( $value ) ) {
      switch( $value['dir'] ) {
        case 'E':
          $multiplier = 1;
          break;
        case 'W':
          $multiplier = -1;
          break;
      }
 
      $t->ok( abs($clean) >= $value['deg'] && abs($clean) <= $value['deg']+1, "value $clean makes sense" );
      $t->ok( $clean * $multipler >= 0, 'direction correct' );
    }
    else {
      $t->is( $value, $clean, 'value makes sense' );
    }
  }
  catch( sfValidatorError $e ) {
    $t->fail( $e->getMessage() );
  }
}
 
foreach ( $lat_bad as $message => $value ) {
  try {
    $lat_validator->clean( $value );
    $t->fail( $message );
  }
  catch( sfValidatorError $e ) {
    $t->pass( $message );
  }
}
 
foreach ( $lon_good as $value ) {
  try {
    $clean = $lon_validator->clean( $value );
    $t->ok( is_numeric( $clean ), 'is numeric' );
 
    if ( is_array( $value ) ) {
      switch( $value['dir'] ) {
        case 'E':
          $multiplier = 1;
          break;
        case 'W':
          $multiplier = -1;
          break;
      }
 
      $t->ok( abs($clean) >= $value['deg'] && abs($clean) <= $value['deg']+1, "value $clean makes sense" );
      $t->ok( $clean * $multipler >= 0, 'direction correct' );
    }
    else {
      $t->is( $value, $clean, 'value makes sense' );
    }
  }
  catch( sfValidatorError $e ) {
    $t->fail( $e->getMessage() );
  }
}
 
foreach ( $lon_bad as $message => $value ) {
  try {
    $lon_validator->clean( $value );
    $t->fail( $message );
  }
  catch( sfValidatorError $e ) {
    $t->pass( $message );
  }
}
 
by yitznewton on 2011-02-27, tagged coordinates  form  latitude  longitude  validator 

Validator for Hebrew-character integers

The Hebrew alphabet has its own traditional representation for integers. Here is a validator to clean them into conventional Arabic integers. There may be gotchas involved with the Unicode processing. I developed this on a 64-bit Ubuntu system.

<?php
class ynValidatorIntegerHebrew extends sfValidatorInteger
{
  protected function configure($options = array(), $messages = array())
  {
    parent::configure( $options, $messages );
 
    // value to add to Hebrew entries, useful for assumed millenia
    $this->addOption('add_to_hebrew', 0);
  }
 
  public function clean( $value )
  {
    if ( preg_match( '/^\p{Hebrew}+(?:"\p{Hebrew})?$/u', $value ) ) {
      $value = $this->convertStringToInt( $value );
    }
 
    return parent::clean( $value );
  }
 
  /**
   * Iterates through the Hebrew characters in the string, converting
   * each to its Unicode code and evaluating for numerical value
   *
   * @param string $value The integer represented in Hebrew characters
   * @return integer
   */
  protected function convertStringToInt( $value )
  {
    if ( ! is_string( $value ) ) {
      throw new UnexpectedValueException( 'Argument must be a string' );
    }
 
    preg_match_all( '/\p{Hebrew}/u', $value, $matches );
 
    $value_int = 0;
 
    foreach ( $matches[0] as $char ) {
      $char     = iconv( 'UTF-8', 'UTF-32', $char );
      $char_dec = unpack( 'L*', $char );
      $char_dec = $char_dec[2];
 
      if ( $char_dec > 1510 ) {
        $base = $char_dec - 1510;
        $value_int += $base * 100;
      }
      elseif ( $char_dec > 1496 ) {
        $base = $char_dec - 1496;
 
        // have to deal with final forms
        if ( $base > 2 ) $base--;
        if ( $base > 4 ) $base--;
        if ( $base > 5 ) $base--;
        if ( $base > 8 ) $base--;
        if ( $base > 9 ) $base--;
 
        $value_int += $base * 10;
      }
      else {
        $base = $char_dec - 1487;
        $value_int += $base;
      }
    }
 
    $value_int += $this->getOption('add_to_hebrew');
 
    return $value_int;
  }
}
 
// UNIT TEST
 
// The Hebrew literals are displaying oddly because of right-to-left; it should
// still work
 
require_once dirname(__FILE__).'/../../bootstrap/Doctrine.php';
 
$t = new lime_test();
 
$val = new ynValidatorIntegerHebrew();
 
$t->is( $val->clean('א'), 1 );
$t->is( $val->clean('ב'), 2 );
$t->is( $val->clean('ג'), 3 );
$t->is( $val->clean('ד'), 4 );
$t->is( $val->clean('ה'), 5 );
$t->is( $val->clean('ו'), 6 );
$t->is( $val->clean('ז'), 7 );
$t->is( $val->clean('ח'), 8 );
$t->is( $val->clean('ט'), 9 );
$t->is( $val->clean('י'), 10 );
$t->is( $val->clean('כ'), 20 );
$t->is( $val->clean('ך'), 20 );
$t->is( $val->clean('ל'), 30 );
$t->is( $val->clean('מ'), 40 );
$t->is( $val->clean('ם'), 40 );
$t->is( $val->clean('נ'), 50 );
$t->is( $val->clean('ן'), 50 );
$t->is( $val->clean('ס'), 60 );
$t->is( $val->clean('ע'), 70 );
$t->is( $val->clean('פ'), 80 );
$t->is( $val->clean('ף'), 80 );
$t->is( $val->clean('צ'), 90 );
$t->is( $val->clean('ץ'), 90 );
$t->is( $val->clean('ק'), 100 );
$t->is( $val->clean('ר'), 200 );
$t->is( $val->clean('ש'), 300 );
$t->is( $val->clean('ת'), 400 );
$t->is( $val->clean('תשע'), 770 );
$t->is( $val->clean('תתרתשע'), 1770 );
 
$val->setOption('add_to_hebrew', 5000);
$t->is( $val->clean('תשע'), 5770 );
 
by yitznewton on 2011-02-17, tagged hebrew  integer  validator 

Validator and widget for fuzzy dates

If I understand correctly, ISO-8601 allows for dates represented as YYYY, YYYY-MM, and YYYY-MM-DD, among others. I did not see the possibility of supporting these date formats with the built-in widgets and validators, so I wrote some.

class ynWidgetFormDateFuzzy extends sfWidgetFormDate
{
  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
    // convert value to an array
    $default = array('year' => null, 'month' => null, 'day' => null);
    if (is_array($value))
    {
      $value = array_merge($default, $value);
    }
    else if ( preg_match( '/^(\d+)$/', $value, $matches ) )
    {
      $value = array(
        'year'  => $matches[1],
        'month' => '',
        'day'   => '',
      );
    }
    else if ( preg_match( '/^(\d+)-(\d+)$/', $value, $matches ) )
    {
      $value = array(
        'year'  => $matches[1],
        'month' => $matches[2],
        'day'   => '',
      );
    }
    else
    {
      $value = (string) $value == (string) (integer) $value ? (integer) $value : strtotime($value);
      if (false === $value)
      {
        $value = $default;
      }
      else
      {
        $value = array('year' => date('Y', $value), 'month' => date('n', $value), 'day' => date('j', $value));
      }
    }
 
    $date = array();
    $emptyValues = $this->getOption('empty_values');
 
    $date['%day%'] = $this->renderDayWidget(
      $name.'[day]',
      $value['day'] ? (int) $value['day'] : '',
      array(
        'choices' => array('' => $emptyValues['day']) + $this->getOption('days'),
        'id_format' => $this->getOption('id_format')
      ),
      array_merge($this->attributes, $attributes)
    );
 
    $date['%month%'] = $this->renderMonthWidget(
      $name.'[month]',
      $value['month'] ? (int) $value['month'] : '',
      array(
        'choices' => array('' => $emptyValues['month']) + $this->getOption('months'),
        'id_format' => $this->getOption('id_format')
      ),
      array_merge($this->attributes, $attributes)
    );
 
    $date['%year%'] = $this->renderYearWidget($name.'[year]', $value['year'], array('choices' => $this->getOption('can_be_empty') ? array('' => $emptyValues['year']) + $this->getOption('years') : $this->getOption('years'), 'id_format' => $this->getOption('id_format')), array_merge($this->attributes, $attributes));
 
    return strtr($this->getOption('format'), $date);
  }
}
 
 
class ynValidatorDateFuzzy extends sfValidatorBase
{
  protected function configure($options = array(), $messages = array())
  {
    $this->addMessage('max', 'The date must be before %max%.');
    $this->addMessage('min', 'The date must be after %min%.');
 
    $this->addOption('min', null);
    $this->addOption('max', null);
  }
 
  protected function doClean($value)
  {
    if ( ! is_array( $value ) ) {
      throw new sfValidatorError($this, 'bad_format');
    }
 
    // convert array to date string
    if (is_array($value))
    {
      $value = $this->convertDateArrayToString($value);
    }
 
    if ($max = $this->getOption('max')) {
      $date_max = new DateTime( $max );
 
      if ( $this->lowestPossible( $value )->format('Ymd') > $date_max->format('Ymd') ) {
        throw new sfValidatorError($this, 'max', array('value' => $value, 'max' => 'out of range'));
      }
    }
 
    if ($min = $this->getOption('min')) {
      $date_min = new DateTime( $min );
 
      if ( $this->highestPossible( $value )->format('Ymd') < $date_min->format('Ymd') ) {
        throw new sfValidatorError($this, 'max', array('value' => $value, 'min' => 'out of range'));
      }
    }
 
    return $value;
  }
 
  protected function convertDateArrayToString($value)
  {
    // all elements must be empty or a number
    foreach (array('year', 'month', 'day', 'hour', 'minute', 'second') as $key)
    {
      if (isset($value[$key]) && !preg_match('#^\d+$#', $value[$key]) && !empty($value[$key]))
      {
        throw new sfValidatorError($this, 'invalid', array('value' => $value));
      }
    }
 
    // check empty value correspondence
 
    if (
      ! $value['year'] && ! $value['month'] && ! $value['day']
    ) {
      return $this->getEmptyValue();
    }
    else if (
      ! $value['year']
      && ($value['month'] || $value['day'])
    ) {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }
    else if (
      $value['year'] && ! $value['month'] && $value['day']
    ) {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }
 
    if ( $value['month'] && ! in_array( (int) $value['month'], range(1,12) ) ) {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }
 
    if ( $value['day'] && ! in_array( (int) $value['day'], range(1,31) ) ) {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }
 
    $clean = '';
 
    if ( $value['year'] ) {
      $clean .= sprintf( '%04d', intval( $value['year'] ) );
    }
 
    if ( $value['month'] ) {
      $clean .= sprintf( '-%02d', intval( $value['month'] ) );
    }
 
    if ( $value['day'] ) {
      $clean .= sprintf( '-%02d', intval( $value['day'] ) );
    }
 
    return $clean;
  }
 
  protected function highestPossible( $date )
  {
    if ( preg_match( '/^\d+$/', $date, $matches ) ) {
      return new DateTime( $date . '-12-31' );
    }
    else if ( preg_match( '/^\d+-(\d+)$/', $date, $matches ) ) {
      switch ( $matches[1] ) {
        case '2':
          return new DateTime( $date . '-28' );
        case '1':
        case '3':
        case '5':
        case '7':
        case '8':
        case '10':
        case '12':
          return new DateTime( $date . '-31' );
        default:
          return new DateTime( $date . '-30' );
      }
    }
    else {
      return new DateTime( $date );
    }
  }
 
  protected function lowestPossible( $date )
  {
    if ( preg_match( '/^\d+$/', $date, $matches ) ) {
      return new DateTime( $date . '-01-01' );
    }
    else if ( preg_match( '/^\d+-(\d+)$/', $date, $matches ) ) {
      return new DateTime( $date . '-01' );
    }
    else {
      return new DateTime( $date );
    }
  }
}
 
by yitznewton on 2011-02-16, tagged date  validator  widget