OK, this may be completely obvious to everyone, but I’m a newbie and didn’t see how to do it, so I thought I’d share. I needed to validate that a registering user entered city, state, and zip code data that was internally consistent. I have a table populated with data from zipcodes.com which provides the data for that validation.
My validation looks like:
class CityStateZipValidator extends CValidator
{
public $skipOnError=true;
protected function validateAttribute($object,$attribute)
{
$value=$object->$attribute;
if ($object->CountryCode != 'US')
return;
if ($this->isEmpty($object->Zip))
return;
if ($this->isEmpty($object->City))
return;
if ($this->isEmpty($object->State))
return;
$zips = ZipCode::model()->findAll('ZIPCODE=?',array($object->Zip));
if (count($zips) == 0)
{
$this->addError($object,'Zip','Invalid zip/postal code specified.',array('{value}'=>$object->Zip));
return;
}
$citymatch = false;
$statematch = false;
$city_to_match = strtolower($object->City);
foreach ($zips as $zip)
{
if (strtolower($zip->GetAttribute('CITY')) == $city_to_match ||
strtolower($zip->GetAttribute('CITYALIASNAME')) == $city_to_match ||
strtolower($zip->GetAttribute('CITYALIASABBREVIATION')) == $city_to_match)
{
$citymatch = true;
}
if ($zip->GetAttribute('STATE') == $object->State)
{
$statematch = true;
}
}
if(! $statematch)
{
$this->addError($object,'State','The state selected doesn\'t match the zip/postal code specified. Did you mean '.$zips[0]->GetAttribute('STATE').'?',array('{value}'=>$object->State));
}
if(! $citymatch)
{
$this->addError($object,'City','The city specified doesn\'t match the zip/postal code. Did you mean '.$zips[0]->GetAttribute('CITY').'?',array('{value}'=>$object->Zip));
}
}
}
Obviously, this code should be made more flexible and accept a lot of the hard-coded model fields as parameters, make the error messages translateable, etc, but for a quick and dirty approach, it will work.