Hi everyone…
I’ve searched a lot and I’ve never found a specific validator that checks if a composite foreign key exists.
So, I implemented this class and I’d like anyone could test it and give me suggestion how to improve it.
I took as base the Yii’s CExistValidator class.
/**
 * CCompositeExistValidator validates that the composite attribute value exists in a table.
 *
 * This validator is often used to verify that a composite foreign key contains a value
 * that can be found in the foreign table.
 *
 * @author SoulBlighter based on Qiang Xue code
 * @package ext.composite-validators
 */
class CCompositeExistValidator extends CValidator
{
	/**
	 * @var string the ActiveRecord class name that should be used to
	 * look for the attribute value being validated. Defaults to null,
	 * meaning using the ActiveRecord class of the attribute being validated.
	 * You may use path alias to reference a class name here.
	 * @see attributeName
	 */
	public $className;
	/**
	 * @var string the ActiveRecord class attribute name that should be
	 * used to look for the attribute value being validated. Defaults to null,
	 * meaning using the name of the attribute being validated.
	 * @see className
	 */
	public $attributeName;
	/**
	 * @var array additional query criteria. This will be combined with the condition
	 * that checks if the attribute value exists in the corresponding table column.
	 * This array will be used to instantiate a {@link CDbCriteria} object.
	 * @since 1.0.8
	 */
	public $criteria=array();
	/**
	 * @var boolean whether the attribute value can be null or empty. Defaults to true,
	 * meaning that if the attribute is empty, it is considered valid.
	 */
	public $allowEmpty=true;
	/**
	 * Validates the attribute of the object.
	 * If there is any error, the error message is added to the object.
	 * @param CModel $object the object being validated
	 * @param string $attribute the attributes being validated separated by pipe
	 */
	protected function validateAttribute($object,$attribute)
	{
		$attributes = explode('|', $attribute);
		$values = array();
		foreach($attributes as $attr) {
			$values[]=$object->$attr;
		}
		
		$allEmpty = true;
		foreach ($values as $value) {
			if(!($this->allowEmpty && $this->isEmpty($value))) {
				$allEmpty = false;
				break;
			}
		}
		if ($allEmpty) {
			return;
		}
		$className=$this->className===null?get_class($object):Yii::import($this->className);
		
		$attributesName=$this->attributeName===null?$attributes:explode('|', $this->attributeName);
		$finder=CActiveRecord::model($className);
		$table=$finder->getTableSchema();
		
		$columns = array();
		foreach($attributesName as $attributeName) {
			if(($column=$table->getColumn($attributeName))===null) {
				throw new CException(Yii::t('yii','Table "{table}" does not have a column named "{column}".',
					array('{column}'=>$attributeName,'{table}'=>$table->name)));
			}
			$columns[] = $column;
		}
		$conditions = array();
		$params = array();
		foreach($columns as $index=>$column) {
			$conditions[] = $column->name.'=:'.$column->name;
			$params[":{$column->name}"] = $values[$index];
		}
		
		$condition = implode(' AND ',$conditions);
		$criteria=array('condition'=>$condition,'params'=>$params);
		if($this->criteria!==array())
		{
			$criteria=new CDbCriteria($criteria);
			$criteria->mergeWith($this->criteria);
		}
		if(!$finder->exists($criteria))
		{
			$message=$this->message!==null?$this->message:Yii::t('yii','The composite key {attribute} "{value}" is invalid.');
			$this->addError($object,str_replace('|', ',', $attribute),$message,array('{value}'=>implode(',',$values)));
		}
	}
}
I’ve made some tests and worked fine for me.
I tried to define the pattern of separating composite key columns using the pipe symbol.
Below, an example of use:
public function rules() {
	return array(
		array('codigo_empresa_requisitante|codigo_filial_requisitante|matricula_requisitante', 
			'ext.composite-validators.CCompositeExistValidator', 
			'allowEmpty'=>false, 
			'className'=>'Funcionario', 
			'attributeName'=>'codigo_empresa|codigo_filial|matricula'),
	);
}
Someone can tell me how to create an alias to call my validator, because I wanted to use ‘compositeExist’ instead of ‘ext.composite-validators.CCompositeExistValidator’?
Thanks for attention.