CakePHP 4 - 如何验证需要将数据保存到多个表的表单

CakePHP 4 - how to validate forms that need to save data to multiple tables

如果之前有人问过这个问题,我们深表歉意。我能找到的所有示例都是旧的或适用于 CakePHP 的旧版本,例如cakephp: saving to multiple models using one form 7 岁。

我在 CakePHP 4.1.6 中有一个应用程序。数据库中的两个 table 称为 tbl_userstbl_orgs(“orgs”在这种情况下表示“组织”)。

当我添加一个组织时,我还想创建一个用户,他是该组织内的主要联系人。这涉及在提交表单时同时保存到 tbl_orgstbl_users table。

我遇到的问题是如何让表单以运行 both tbl_users 和 运行 的验证规则的方式工作tbl_orgs 提交时。

这是我们的应用程序当前的结构:

src/Controller/TblOrgsController.php 中有一个名为 add() 的控制器方法。这是由 bake 生成的,最初用于将新组织插入 tbl_orgs table。此时它在 tbl_users 方面没有做任何事情,但是它在保存新组织和 运行 适当的验证规则方面起作用。

一个验证规则是 tbl_orgs 中的每个 companyname 记录必须是唯一的。如果您尝试插入超过 1 个名称为“My Company Limited”的公司,则会出现验证错误“此公司名称已存在”:

// src/Model/Table/TblOrgsTable.php
public function buildRules(RulesChecker $rules): RulesChecker
{
    $rules->add(
        $rules->isUnique(['companyname']),
        [
            'errorField' => 'companyname',
            'message' => 'This company name already exists',
        ]
    );

    return $rules;
}

虽然上述内容适用于 TblOrgs,但我们在 TblUsers 中也有一个 buildRules(),它在 email 字段上应用类似的逻辑,以确保所有电子邮件地址都是每个用户唯一。

add() 控制器方法中,我们首先为 TblOrgs 指定一个新的空实体:

// src/Controller/TblOrgsController.php
public function add()
{
    $org = $this->TblOrgs->newEmptyEntity();
    // ...
    $this->set(compact('org'));
}

创建表单时我们传递 $org:

// templates/TblOrgs/add.php
<?= $this->Form->create($org) ?>
<?= $this->Form->control('companyname') ?>
<?= $this->Form->end() ?>

当浏览器呈现 TblOrgs 字段时,我们可以检查 HTML 并查看它们是否服从相应的模型。这很清楚,因为 required="required"maxlength="100" 之类的东西对应于字段不允许为空并且是数据库中的 VARCHAR(100) 字段的事实:

<input type="text" name="companyname" required="required" id="companyname" maxlength="100"> 

它也适用于 buildRulesTblOrgs 指定的规则。例如,如果我两次输入相同的公司名称,它会在线显示相应的错误:

然后我尝试为 TblUsers 引入字段。我在表单字段前加上点符号,例如这旨在对应于 tbl_users.email 输入字段:

<?= $this->Form->control('TblUser.email') ?>

当检查 HTML 时,它不会做与 TblOrgs 相同的事情。例如 maxlengthrequired 之类的内容不存在。它实际上并不知道 TblUsers。我知道我的 Controller 方法中的 $org 是为 TblOrgs 而不是 TblUsers 指定一个新实体。我查看了 Saving With Associations 上的 CakePHP 文档,上面写着

The save() method is also able to create new records for associations

但是,在文档中给出的示例:

$firstComment = $articlesTable->Comments->newEmptyEntity();
// ...
$tag2 = $articlesTable->Tags->newEmptyEntity();

在这种情况下,Tags 是与 Comments 不同的模型,但 newEmtpyEntity() 对两者都适用。考虑到这一点,我将 add() 方法调整为:

$org = $this->TblOrgs->TblUsers->newEmptyEntity();

但这现在为 TblUsers 提供了一个实体。看来你可以选择其中之一,但不能同时选择两者。

这对我的用例不起作用的原因是我可以 运行 我的 TblOrgs(但不是 TblUsers)的验证规则,反之亦然。

您如何以 运行 both 模型的验证规则的方式进行设置?一个表单可能需要将数据保存到多个 table,并且您希望每个表单的验证规则都为 运行,这似乎并不是一个不合理的要求。我从文档中得到的印象是这是可能的,但不清楚如何实现。

供参考两者之间的适当关系table:

// src/Model/Table/TblOrgsTable.php
public function initialize(array $config): void
{
    $this->hasMany('TblUsers', [
        'foreignKey' => 'o_id',
        'joinType' => 'INNER',
    ]);
}

// src/Model/Table/TblUsersTable.php
public function initialize(array $config): void
{
    $this->belongsTo('TblOrgs', [
        'foreignKey' => 'o_id',
        'joinType' => 'INNER',
    ]);
}

好的,这里有很多混乱需要澄清。 :-) 我在这里的假设是,根据您所写的内容,您正在尝试使用单一表单添加一个新组织,以及其中的第一个用户,然后您可能会在以后添加更多用户组织。

首先,$this->TblOrgs->TblUsers是你的用户table对象,所以当你使用

$org = $this->TblOrgs->TblUsers->newEmptyEntity();

您正在做的是创建一个新的用户实体。您通过组织 table 访问 table 对象并调用它 $org 这一事实并没有改变这一点。它不会以某种方式神奇地创建一个空白的组织实体,其中包含一个空白的用户实体。但是你在这里根本不需要那个实体结构,只需要空的组织实体。回到简单:​​

$org = $this->TblOrgs->newEmptyEntity();

现在,在您的表单中,您需要这样的内容:

<?= $this->Form->create($org) ?>
<?= $this->Form->control('companyname') ?>
<?= $this->Form->control('tbl_users.0.email') ?>
<?= $this->Form->end() ?>

该字段被称为 tbl_users.0.email 因为:

  1. table 名称被转换为小写下划线格式。
  2. 它是一个从组织到用户的 hasMany 关系,所以它需要一组用户;我们必须为该数组提供一个数字索引,0 是一个很好的起点。如果您要同时添加第二个用户,该字段将为 tbl_users.1.email.

注意:确定表单助手希望您以何种格式创建字段名称的一种好方法是从数据库中读取一组现有记录(在本例中为组织及其用户),然后用 debug($org); 之类的东西转储该数据。您会看到 $org 有一个名为 tbl_users 的 属性,它是一个数组,它将直接指向我在上面描述的这个结构。

像这样设置字段后,您应该能够将结果数据直接修补到控制器中的 $org 实体中,并保存它而无需任何其他工作。补丁将创建整个结构,实体为 class TblOrgtbl_users 属性 是一个包含单个实体 class TblUser,并且将对它们进行验证。 (至少它应该;你可以使用上面提到的 debug($org); 来确认它。)当你保存这个实体时,它会首先保存 TblOrg 实体,然后将那个新 ID 添加到 TblUser 实体,然后检查两者的规则,并确保如果无法全部保存,则不会将任何内容保存到数据库中。只需一次 save 调用,这一切都会自动发生!

如果您的关联是 hasOne 或 belongsTo 关系(例如,如果您要添加新用户以及他们所在的组织,而不是相反),您可以转储示例 $user,并看到它有一个名为 tbl_org 的 属性,它只是一个直接的实体,而不是实体数组,请注意 tbl_org 现在是单数的,因为它只是一个实体而不是一堆。在这种情况下,要使用的字段名称将是 tbl_org.companyname,其中根本没有数组索引。