How to? Validation of self referencing table data

Hi,

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
https://book.cakephp.org/3.0/en/orm/validation.html#applying-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(
                $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;
    }

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
https://book.cakephp.org/3.0/en/core-libraries/validation.html#adding-validation-providers

$this->MachineParts
     ->getValidator('default')
     ->setProvider('machine_parts', $machine->machine_parts);

then you can access it in your custom validation rule from $context['providers']['machine_parts']
https://book.cakephp.org/3.0/en/core-libraries/validation.html#using-custom-validation-rules