Custom validation on a key with a null value doesn't trigger

$data = [
       'item' => null
];

$validator = new Validator(); 

$validator->notEmptyArray('item', 'Item needs to be linked to this production line');

$errors = $validator->validate($data);
dd($errors); 
// output

[
'item' => [
'_empty' => 'Item needs to be linked to this production line',
],
]


But if I replace the notEmptyArray() with a custom rule the custom rule never fires

$validator
            ->add('item', 'customRule', [
                'rule' => function ($value, $context) {
                    dd('never fires');
                },
            ]);

$errors = $validator->validate($data);
dd($errors); 

[
'item' => [
'_empty' => 'This field cannot be left empty',
],
]

Anyone know what combination of validation rules I need so that a null value triggers the custom function?

I’ve tried custom provider with custom rules and a heap of variations with requirePresence but it doesn’t seem to allow me to dynamically modify the error message output if the key value is null.

You can try putting it inside another function

// inside /src/Model/Table/UsersTable.php,
 public function validationDefault(Validator $validator): Validator

	$validator->add( 'amount', 'custom', [
		'rule' => [$this, 'positiveNumber'],
		'message' => 'Number cannot be negative.'
	]);
	....

}


// inside the same UsersTable.php
public function positiveNumber($value,$context){
	if( $value < 0 ) return false;
	return true;
	
}

Found if I extend Validator and over ride the validate() method and comment out the isEmpty bits it will now hit the custom validation rules

  public function validate(array $data, bool $newRecord = true): array
    {
            //... snippage
            // comment out the bits that cause a null value to skip custom rule processing  
            // $isEmpty = $this->isEmpty($data[$name], $flags);

            //  if (!$canBeEmpty && $isEmpty) {

            //      $errors[$name]['_empty'] = $this->getNotEmptyMessage($name);
            //      continue;
            // }

            // if ($isEmpty) {
            //     continue;
            // }
            // now correctly processes my rules because it doesn't skip this bit.
           $result = $this->_processRules($name, $field, $data, $newRecord);
            if ($result) {
                $errors[$name] = $result;
            }
 $validator = new \App\Validation\Validator();

        $validator
            ->add('item', 'checkItem', [
                'rule' => function ($value, $context) {
                    if (is_null($value)) {
                        return __(
                            "You need to specify a value for line \"{0}\"",
                            $context['data']['name']
                        );
                    }
                    return true;
                }
            ]);

        $data = [
            'item' => null,
            'name' => 'Production Line Name Here'
        ];

        $errors = $validator->validate($data);

        dd($errors);
    }

Output

[
  'item' => [
    'checkItem' => 'You need to specify a value for line "Production Line Name Here"'
  ]
]

Cake’s validator first makes the determination of whether the value can be empty. If it’s not provided at all in the data, and it can be empty, then it doesn’t throw an error, and won’t call other validation. What you probably want is a notEmptyArray call and ALSO your custom validation.

Sadly once notEmptyArray is specified the custom rules are skipped.

So the only way around it, it seems is to override the validate() method.

 $isEmpty = $this->isEmpty($data[$name], $flags);

            if (!$canBeEmpty && $isEmpty) {
               // this is the only rule that is hit with notEmptyArray
               // the continue causes skipping custom rules
                $errors[$name]['_empty'] = $this->getNotEmptyMessage($name);
                continue;
            }

            if ($isEmpty) {
                continue;
            }

            $result = $this->_processRules($name, $field, $data, $newRecord);
            if ($result) {
                $errors[$name] = $result;
            }

Is an empty list valid for you or not? It seems from what you’ve said that it is not. So it should be fine for it to first check if it’s empty and give an error if it is. Then if it’s not empty, run your extra validation on the data provided. What’s the problem with this approach?