How to? Validation of self referencing table data


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.

You have to use application rules

Thank you for your hint.

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(

     * 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;

It does not seems hacky to me. And I think you do not have any other options.

for validation you can set provider as any object even your missing machine_parts

     ->setProvider('machine_parts', $machine->machine_parts);

then you can access it in your custom validation rule from $context['providers']['machine_parts']