I have a hasMany relation between the tables “machines” and “machine_parts”.
The user uploads an XML file with a whole machine: some header-information and the parts, the machine consists of.
Here comes my problem:
Machine parts can be mounted on each other. This field (mounted_on) can also be empty.
Before saving the data I want to validate if all machine parts with the mounted_on field set contain a valid reference to an existing machine part.
The validation errors should be attached to the “mounted_on” field.
I wasn’t able to achive this with custom validation rules, because $context gives me only access on an entity (row) level. I wasn’t able to access the whole array of “machine_parts”.
'rule' => function ($value, $context)
I was able to get my desired behavior within the buildRules-Event. But this feels quite hacky because there I’m using ->setError(...) to inject the errors on the field level.
How ist this done in the cakephp-way? Is my approach right?
P.S.: The reason why I choose to do the validation on the table level and not inside the xml-parser was that I want to keep all the validation in one place.
Another approach could be to add methods to the “machine_parts”-table that set a property containing invalid “mounted_on”-fields. Then I could compare them in the validation rules with ->inList(). Maybe this works with beforeMarshal. Seems a “cleaner” solution to me. But still feels hacky.
I’ll try this tomorrow. Hope someone can point me in the right direction.
I did exactly this (in the MachinesTable). But this way the errors obviously don’t get attached to the machine part field (mounted_on) but on the association (mounted_parts).
My current rule in the MachinesTable looks like the following code. It works, but feels quite hacky. Isn’t there any possibility without setting the errors manually on the associated data?
$rules->add(function ($entity, $options) {
$invalidMountPoints = $this->getInvalidMountpoints($entity['machine_parts']);
return $this->setErrorOnAssociated(
$invalidMountPoints,
'mounted_on',
__('machine_parts.validation_error.mounted_on_does_not_exist')
);
},
'checkMounPointExists');
/**
* Set errors on an associated entity
*
* @param array &$errEntities an array containing entities with errors
* @param string $field The field on which the error should be set
* @return bool Returns true if the entity contains no errors
*/
public function setErrorOnAssociated(?array &$errEntities, string $field, string $errorMsg): bool
{
if(!empty($errEntities)) {
foreach($errEntities as &$errEntity) {
$errEntity->setError($field, $errorMsg);
}
return false;
}
return true;
}