CakePHP 3.0.8 转换行为和数据验证(requirePresence、notEmpty)

CakePHP 3.0.8 translate behavior and data validation (requirePresence, notEmpty)

我的问题很简单,但我不知道如何解决。

我的网站是多语言的。我希望用户能够根据需要添加多种语言的文章,同时要求输入他的语言(取决于他的语言环境)。

问题是,根据 CakePHP 的翻译约定,所有输入都必须以字段名称结尾,无论是哪种语言。所以所有的字段对同一个字段都有相同的规则。我不能让一个 "name" 成为必需而另一个不需要另一种语言。

例如,默认语言的输入为:

<input type="text" name="name" required="required" maxlength="45" id="name">

在此之下,同一字段的另一种语言输入:

<input type="text" name="locales[fr_CA][name]" required="required" maxlength="45" id="locales-fr-ca-name">

由于这些规则,"required" 属性会自动添加到两者:

$validator
    ->requirePresence('name', 'create')
    ->notEmpty('name')
    ->add('name', [
        'length' => [
            'rule' => ['minLength', 10],
            'message' => 'The title needs to be at least 10 characters long.',
        ]
    ]);

注意:我必须在保存时将区域设置更改为默认设置(en_US)才能以多种语言保存+默认语言(否则默认输入保存在默认table 并且在 i18n table).

if ($this->request->is('post')) {
    I18n::locale('en_US');
    // ......

编辑:这是我保存时的完整代码 (IngredientsController.php)

public function add() {
    $ingredient = $this->Ingredients->newEntity();
    if ($this->request->is('post')) {
        $ingredient = $this->Ingredients->patchEntity($ingredient, $this->request->data);

        if(isset($this->request->data['locales'])) {
            foreach ($this->request->data['locales'] as $lang => $data) {
                $ingredient->translation($lang)->set($data, ['guard' => false]);
            }
        }

        $locale = I18n::locale(); // At this point the locale is fr_CA (not de default)
        I18n::locale('en_US'); // Change the locale to the default

        if ($this->Ingredients->save($ingredient)) {
            $this->Flash->success(__('The ingredient has been saved.'));
            I18n::locale($locale); // Put the locale back to the user's locale
            return $this->redirect(['action' => 'index']);
        } else {
            I18n::locale($locale);
            $this->Flash->error(__('The ingredient could not be saved. Please, try again.'));
        }
    }

    $this->set(compact('ingredient'));
    $this->set('_serialize', ['ingredient']);
}

我设置的默认语言环境是bootstrap.php

/**
 * Set the default locale. This controls how dates, number and currency is
 * formatted and sets the default language to use for translations.
 */
ini_set('intl.default_locale', 'en_US');
Configure::write('Config.locales', ['fr_CA']);

我在 AppController.php

中确定用户的语言环境
public function beforeFilter(Event $event)
{
    $locales = Configure::read('Config.locales');
    $boom = explode(',', str_replace('-', '_', $_SERVER['HTTP_ACCEPT_LANGUAGE']));
    $user_lang = substr($boom[0], 0, 2);

    // This piece of code is only to change the locale to fr_CA even if the user's language is just fr or fr_FR
    if(in_array($user_lang, Configure::read('Config.langs'))) {
        if(in_array($boom[0], $locales)) {
            I18n::locale($boom[0]);
        } else {
            foreach ($locales as $locale) {
                if(substr($locale, 0, 2) == $user_lang) {
                    I18n::locale($locale);
                }
            }
        }
    }

    $this->set('locales', $locales);
    $this->set('locale', I18n::locale());
}

因此,如果我在与默认设置不同的区域设置中保存,相同的默认输入将保存在成分 table 和 i18n table 中 fr_CA

默认保存在翻译中table

默认语言的输入被存储在翻译 table 中,以防默认语言环境已更改,这似乎是预期的行为,就像读取数据时将检索的地方一样相对于当前语言环境的数据,保存数据时同样适用。

Cookbook > Database Access & ORM > Behaviours > Translate > Saving in Another Language

将区域设置更改为默认值是一种解决方法,但它可能有点过于侵入性,因为它会干扰使用该值检查当前区域设置的任何代码。最好直接在 table

上设置所需的语言环境
$Ingredients->locale(I18n::defaultLocale());

或者,这是侵入性最小的选项,而不是在主要实体上

$ingredient->_locale = I18n::defaultLocale();

此外,前者是链接文档部分描述的内容,但并未实际显示,需要修复。

选择"wrong"验证规则的字段

虽然我明白为什么表单助手(分别是实体上下文)为 "wrong" 字段选择验证规则,即 xyz.name 字段为 name 字段选择验证规则,我不知道这是否是工作的方式。

由于它不会发现嵌套错误,我想这是预期的行为,但我不确定,所以我建议 create an issue over at GitHub 进行澄清。无论如何,有多种方法可以解决此问题,例如重命名字段,或将 required 选项设置为 false.

echo $this->Form->input('locales.fr_CA.name', [
    // ...
    'required' => false
]);

在您的示例中,这几乎只是一个前端问题,因为不会在服务器端实际验证这些字段。

另一种选择是使用自定义翻译 table class,并针对翻译进行验证,这实际上适用于所使用的字段,但这可能不是 除非您真的想应用任何验证,否则建议这样做。

将 validation/application 规则应用于已翻译的列

为了完成,让我们也涵盖 validation/application 规则。

为了实际应用验证 and/or 应用程序规则,并在表单中识别它们,您将使用包含规则的自定义翻译 table class,并且您必须使用翻译行为用于 hasMany 关联翻译 table 的实际 属性 名称,即 _i18n.

这是一个例子。

src/Model/Table/IngredientsI18nTable.php

namespace App\Model\Table;

use Cake\Datasource\EntityInterface;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

class IngredientsI18nTable extends Table
{
    public function initialize(array $config) {
        $this->entityClass('Ingredient');
        $this->table('i18n');
        $this->displayField('id');
        $this->primaryKey('id');
    }

    public function validationDefault(Validator $validator) {

        $validator
            ->allowEmpty('name')
            ->add('name', 'valid', [
                'rule' => function ($value, $context) {
                    return false;
                }
            ]);
        return $validator;
    }

    public function buildRules(RulesChecker $rules)
    {
        $rules->add(
            function (EntityInterface $entity, $options) {
                return false;
            },
            'i18nName',
            [
                'errorField' => 'name'
            ]
        );

        return $rules;
    }
}

成分表

public function initialize(array $config) {
    // ...

    $this->addBehavior('Translate', [
        // ...
        'translationTable' => 'IngredientsI18n'
    ]);
}

查看模板

echo $this->Form->hidden('_i18n.0.locale', ['value' => 'fr_FR']);
echo $this->Form->input('_i18n.0.name');

echo $this->Form->hidden('_i18n.1.locale', ['value' => 'da_DK']);
echo $this->Form->input('_i18n.1.name');

// ...

现在字段将选择正确的验证器,因此不会被标记为必填。当 creating/patching 个实体时也会应用验证,最后也会应用应用程序规则。但是我不能保证这没有任何副作用,因为内部的翻译行为似乎没有考虑到 _i18n 属性 已在外部设置的情况!

此外,您仍然需要使用 translations() 在实体上设置翻译,以便正确保存翻译!

foreach ($this->request->data['_i18n'] as $translation) {
    $ingredient->translation($translation['locale'])->set('name', $translation['name']);
}