Valitron using a custom rule and the $fields array

I’m trying to do email verification to make sure there are no duplicate guests in a event.

Here is the custom valitron rule

\Valitron\Validator::addRule('checkEmail', function($field, $value, array $params, array $fields)
{

	
	$db2 = db_connect('registration');

	$builder2 = $db2->table('guests');

	$builder2->where('EventYear', $_SESSION["EventYear"]);
	$builder2->where('Email', $value); 
    $query2 = $builder2->get();

    $rowcount = $query2->getNumRows();
    
    //Add new record:
    // if unique email $rowcount = 0; if previously used > 0 (supposed to be 1)
    //update of a record
    //  $rowcount = 1 if no change to email address
    //  $rowcount = 0 if new (unique) email address
    //  $rowcount = 1 if changed and another person has that email address already
    
	if ($rowcount != 0) {
		if ($rowcount == 1) {
			// Need to check if the current ContactID is what was found
			
			$row2 = $query2->getRow();
			log_message ('debug', "rowcount ".$rowcount);
			if ( isset($fields['ContactID'] )) {
				log_message ('debug',"fields ContactID ".$fields['ContactID']);
			} else {
				log_message ('debug',"fields ContactID is not set");
			}
			log_message ('debug', "row ContactID ".$row2->ContactID);
			log_message ('debug', print_r($fields, true));
			
			if ( isset($fields['ContactID']) &&
				($row2->ContactID == $fields['ContactID']) )	{

				 return true;
			}  
			
		}
		return false;

	}
	return true;
	

},'Someone has already invited that person since the email already exists on the guest list. Email addresses MUST be unique.该客户已被邀请,邮箱地址已出现在客户列表上。邮箱地址不能重复。');

Am I using $fields array incorrectly?

In case it makes a difference ‘ContactID’ is the primary key and it is in the fields of the crud as a readonly field type.

$crud->setRule('Email','required');
$crud->setRule('Email','email');
$crud->setRule('Email','checkEmail');

we do have 3 separate setRules for the email field but the other ones don’t seem to he having an issue.

Special Notes*

When we have ‘ContactID’ hidden valitron passes ‘ContactID’ into the $fields array but only on edits. It throws and error when adding a new record.

When we have ‘ContactID’ invisible valitron does not pass ‘ContactID’ into the $fields array
When we have ‘ContactID’ readonly valitron does not pass ‘ContactID’ into the $fields array

CRITICAL - 2025-10-17 07:54:19 --> [Caused by] PDOException: SQLSTATE[22007]: Invalid datetime format: 1366 Incorrect integer value: '' for column `bitswork_registration`.`guests`.`ContactID` at row 1
in VENDORPATH/scoumbourdis/laminas-db/src/Adapter/Driver/Pdo/Statement.php on line 212.
1 VENDORPATH/scoumbourdis/laminas-db/src/Adapter/Driver/Pdo/Statement.php(212): PDOStatement->execute()
2 VENDORPATH/grocery-crud/enterprise/src/GroceryCrud/Core/Model.php(519): Laminas\Db\Adapter\Driver\Pdo\Statement->execute()
3 VENDORPATH/grocery-crud/enterprise/src/GroceryCrud/Core/State/InsertState.php(101): GroceryCrud\Core\Model->insert()
4 VENDORPATH/grocery-crud/enterprise/src/GroceryCrud/Core/State/StateAbstract.php(1262): GroceryCrud\Core\State\InsertState->{closure:GroceryCrud\Core\State\InsertState::render():93}()

Hello @iradave ,

You are doing everything correctly it is just that in Grocery CRUD in Valitron, I am only passing the fields (and not the primary key value) that the user will send and more specifically this is the code:

$validator = new Validator($this->_data);

Since your validation is something really custom and you will need pretty much all of the information, I would go with you using callbackBeforeUpdate instead. More specifically this will look like this:

$crud->callbackBeforeUpdate(function ($stateParameters) {

    $db2 = db_connect('registration');
    $builder2 = $db2->table('guests');

    $builder2->where('EventYear', $_SESSION["EventYear"]);
    $builder2->where('Email', $stateParameters->data['Email']);
    $query2 = $builder2->get();

    $rowcount = $query2->getNumRows();

    log_message('debug', "callbackBeforeUpdate - rowcount: {$rowcount}");

    // If email already exists in DB
    if ($rowcount != 0) {
        $row2 = $query2->getRow();
        log_message('debug', "Existing record ContactID: {$row2->ContactID}");
        log_message('debug', "Current primaryKeyValue: {$stateParameters->primaryKeyValue}");

        // Case 1: Email belongs to the same ContactID being updated → OK
        if ($rowcount == 1 && $row2->ContactID == $stateParameters->primaryKeyValue) {
            return $stateParameters;
        }

               // Case 2: Email exists for another ContactID → Block update
       $errorMessage = new \GroceryCrud\Core\Error\ErrorMessage();
       return $errorMessage->setMessage("Someone has already invited that person since the email already exists on the guest list. Email addresses MUST be unique. 该客户已被邀请,邮箱地址已出现在客户列表上。邮箱地址不能重复。\n");

    }

    // Case 3: Email is unique → OK
    return $stateParameters;
});

Also few observations. Don’t use set rule required instead use requiredFields instead. For example:

$crud->requiredFields(['Email']);

Also consider a frontend email validation as well so the user will get the invalid email even before sending it:

$crud->fieldType('Email', 'email');

but don’t forget to add it also as a rule:

$crud->setRule('Email','email');

I haven’t tested the above big code by the way. I hope you get the main idea and let me know if that works for you.

Regards
Johnny

Hi @johnny - you rock!

This code worked as-is. All the fixes were my typos :slight_smile:

We did have to do a separate callbackBeforeInsert with a simplified check (i.e. that the $rowcount was 0).

Thank you so much!

1 Like