What am I trying to do?
I have three fields (1 hidden, an id) and the user must complete one of the other two in order to pass validation.
So the user should fail validation if both fields are empty, but pass if one is completed.
1 2 3
A 0 B True
A B 0 True
A 0 0 False
I’m using CakePHP v2.1.3 so have access to the new validation rule enhancements.
The problem
I can’t seem to find a reliable way to check both fields at the same time. I have so far tried looking at $this->data from the model and have found that validation is only passing a single instance of the data at a time. So there doesn’t seem to be a way to compare the fields.
What I have so far
/**
* Custom validation to see if either of the two fields are set, if neither are, then we fail, if at least one is, we pass
* @param array $check
* @return boolean
*/
public function checkAttributes($check){
var_dump($check);
var_dump($this->data);
echo "<hr>";
// Check for an id value picked from a list
if(@is_numeric($check['attribute_value_id']) && isset($this->data['AdvertAttributeValue']['attribute_value_id'])){
return true;
}
// Check for a date value selected
if(@is_array($check['attribute_value_text']) && isset($this->data['AdvertAttributeValue']['attribute_value_text'])){
return true;
}
// Check for a text value
if(@is_string($check['attribute_value_text']) && isset($this->data['AdvertAttributeValue']['attribute_value_text'])){
return true;
}
return false;
}
This doesn’t seem to do the trick as I think it can’t check $this->data because the instance of it doesn’t contain all the relevant fields.
The data
I should also mention that I am passing a large numeric array in. So these fields appear multiple times on the page, currently 12 dimensions. So accessing them directly through $this->data will be hard as they are not named dimensions, but are $this->data['Model'][<num>]['field'] = value
Validation
public $validate = array(
'attribute_value_id'=>array(
'notempty'=>array(
'rule'=>'checkAttributes',
'message'=>'Please select a value for your attribute',
'required'=>true,
),
),
'attribute_value_text'=>array(
'notempty'=>array(
'rule'=>'checkAttributes',
'message'=>'You must enter text for this attribute',
'required'=>true,
),
)
);
Data dump
Here I’ll show the output of the var_dump() above. I have two validation rules in my Model, one for attribute_value_id and also one for attribute_value_text
// An id field selected from a list
array // $check
'attribute_value_id' => string '1' (length=1)
array // $this->data
'AdvertAttributeValue' =>
array
'attribute_value_id' => string '1' (length=1)
'id' => string '' (length=0)
// A text field
// Validating first time around on the id field
array // $check
'attribute_value_id' => string '' (length=0)
array // $this->data
'AdvertAttributeValue' =>
array
'attribute_value_id' => string '' (length=0)
'id' => string '' (length=0)
'attribute_value_text' => string '50' (length=2)
// Validating second time around on the text field
array // $check
'attribute_value_text' => string '50' (length=2)
array // $this->data
'AdvertAttributeValue' =>
array
'attribute_value_id' => string '' (length=0)
'id' => string '' (length=0)
'attribute_value_text' => string '50' (length=2)
// A date field
array // $check
'attribute_value_id' => string '' (length=0)
array // $this->data
'AdvertAttributeValue' =>
array
'attribute_value_id' => string '' (length=0)
'id' => string '' (length=0)
'attribute_value_text' =>
array
'month' => string '06' (length=2)
'day' => string '28' (length=2)
'year' => string '2012' (length=4)
The
saveAll()method just callssaveMany()orsaveAssociated()as appropriate. These methods will, by default, attempt to validate all of the data before saving any of it (via a call tovalidateMany()). However, as you can see in the source, that function validates each item individually, so the validation code won’t have access to other records.As I understand it, you need to cross-validate between multiple records before you save them. Although I’ve never done that, it sounds like a case for validation in the controller. You can make calls to
Model::validate()orModel::validateAll()to ensure internal consistency of records. Then your controller can also implement whatever logic is necessary for your cross-record validation. Once that’s done, you can make the save call with validation disabled:Note that before you do any of this, you’ll have to set your data to the model:
I realize this puts a lot of extra code in the controller that should ideally be in the model. I suppose it’s possible that it could be done via one of the model callbacks, but I’m not sure how.