Laurent
Laurent

Reputation: 307

Validating Translated Entities in CakePHP 3

I'm having some difficulties validating a I18N field in CakePHP3.

The translate behavior is setup like this:

$this->addBehavior('Translate', [
    'fields' => ['name', 'body', 'slug'],
    'validator' => 'default'
]);

Like advertised here: https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#validating-translated-entities

The core validation is working properly. I have a validation rule in validationDefault function that checks if name is not empty and it works fine. Though, I also would like to add some application rules on top of this validation. The "name" field should have a unique value. I don't want to allow multiple entities with the same translated name.

This piece of code apparently doesn't work. CakePHP docs also are quite silent about this matter.

public function buildRules(RulesChecker $rules) {
    // prevent duplicate creation
    $rules->add($rules->isUnique(['name']));
    return $rules;
} 

Is this actually possible? Thanks

Upvotes: 1

Views: 566

Answers (1)

ndm
ndm

Reputation: 60483

What you are doing there is creating a rule for the name field on the main model, this won't affect translations. There is no built-in functionality for that, the behavior only assists with validation rules by making use of the validationTranslated() method in case it exists on your model class, it won't help with application rules.

You'd have to create a custom application rule that checks the translation table, by matching against the field, locale, model and content fields, something along the lines of this:

$rules->add(
    function (EntityInterface $entity) {
        $behavior = $this->behaviors()->get('Translate');
        $association = $this->association($behavior->getConfig('translationTable'));

        $result = true;
        foreach ($entity->get('_translations') as $locale => $translation) {
            $conditions = [
                $association->aliasField('field') => 'name',
                $association->aliasField('locale') => $locale,
                $association->aliasField('content') => $translation->get('name')
            ];

            if ($association->exists($conditions)) {
                $translation->setErrors([
                    'name' => [
                        'uniqueTranslation' => __d('cake', 'This value is already in use')
                    ]
                ]);

                $result = false;
            }
        }

        return $result;
    }
);

Note that this uses the association object rather then the target table, this will ensure that further conditions like the model name are being applied automatically.

Also this requires to set the errors on the entity manually, as they are nested, which isn't supported by the rules checker, currently it can only set errors on the first level entity (see the errorField option).

It should also be noted that it would be possible to modify the rules checker for the translation table association (via the Model.buildRules event), however this would result in the errors being set on new entities that will be put in a separate property (_i18n by default) on the main entity, where the form helper won't find the error, so one would then have to read the error manually, which is a little annoying.

See also

Upvotes: 2

Related Questions