首页 > 解决方案 > CakePHP 4 - 如何验证需要将数据保存到多个表的表单

问题描述

抱歉,如果以前有人问过这个问题。我能找到的所有示例都是旧的或适用于 CakePHP 的旧版本,例如cakephp:使用一种形式保存到多个模型已有7 年的历史。

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

当我添加一个组织时,我还想创建一个作为组织内主要联系人的用户。这涉及在提交表单时同时保存到表tbl_orgs和表中。tbl_users

我遇到的问题是如何让表单以在提交时运行验证规则的方式 工作tbl_userstbl_orgs

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

中调用add()了一个Controller方法src/Controller/TblOrgsController.php。这由生成bake并最初用于将新组织插入tbl_orgs表中。在这一点上,它没有做任何事情,tbl_users但它在保存新组织和运行适当的验证规则方面起作用。

一个验证规则是其中的每条companyname记录都tbl_orgs必须是唯一的。如果您尝试插入超过 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但我们也有一个buildRules()在字段TblUsers上应用类似逻辑的email方法,以确保每个用户的所有电子邮件地址都是唯一的。

add()Controller 方法中,我们首先为 指定一个新的空实体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"> 

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

在此处输入图像描述

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

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

检查 HTML 时,它不会做与 for 等效的操作TblOrgs。例如像maxlengthrequired不存在的东西。它实际上不知道TblUsers. 我知道$org在我的 Controller 方法中为TblOrgsand not指定了一个新实体TblUsers。我查看了关于通过关联保存的 CakePHP 文档,其中说

save()方法还能够为关联创建新记录

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

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

在这种情况下Tags,是一个不同的模型,CommentsnewEmtpyEntity()适用于两者。考虑到这一点,我将我的add()方法调整为:

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

但这现在为TblUsers. 似乎你可以有一个或另一个,但不能同时拥有。

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

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

作为参考,这两个表之间存在适当的关系:

// 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',
    ]);
}

标签: phpcakephpcakephp-4.x

解决方案


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

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

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

您正在做的是创建一个新的用户实体。您通过 orgs 表获得该表对象并且您正在调用它的事实$org并没有改变这一点。它不会以某种方式神奇地创建一个包含空白用户实体的空白组织实体。但是这里根本不需要那个实体结构,只需要空的 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. 表名被转换为小写下划线格式。
  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 关系(例如,如果您要添加新用户以及他们所在的组织,而不是相反),您可以转储一个 sample $user,并查看它有一个属性称为tbl_org它只是一个直接的实体,而不是一组实体,请注意tbl_org现在是单数的,因为它只是一个实体而不是一堆。在这种情况下,要使用的字段名称是tbl_org.companyname,其中根本没有数组索引。


推荐阅读