CakePHP:无法通过 belongsTo 关联的子实体将数据保存到父实体

CakePHP: Unable to save data to parent entity via belongsTo associated child

问题描述

我正在尝试配置 CakePHP 3.7 API 以儿童优先的方式保存相关数据。实体 - 为了举例,我们称它们为 UsersPersons - 它们的关系如下:

UsersTable.php

...
  $this->belongsTo('Persons', [
    'foreignKey' => 'person_id',
    'joinType' => 'LEFT',
    'className' => 'MyPlugin.Persons',
  ]);
...

PersonsTable.php

  $this->hasOne('Users', [
    'foreignKey' => 'person_id',
    'className' => 'MyPlugin.Users'
  ]);

在他们各自的实体中,他们彼此的 属性 可见性设置为 true。我想要做的是 POST/users/ 路线 (UsersController.php) 并让它也保存包含的 Persons 对象。有效载荷是这样的:

{
    "username": "foo",
    "password": "bar",
    "persons": {
        "dob": "1982-07-03",
    }
}

保存方法的相关部分如下,来自UsersController.php

  if ($this->request->is('post') && !empty($this->request->getData())) {
    $data = $this->request->getData();
    $newEntity = $this->Users->newEntity($data, ['associated' => 'Persons']);

    $savedEntity =  $this->Users->save($newEntity);
  ...

错误

这会产生以下 SQL 错误。

PDOException: SQLSTATE[23502]: Not null violation: 7 ERROR: null value in column 'person_id' violates not-null constraint DETAIL: Failing row contains (1, null, foo, bar)

我知道这是因为 Cake 试图保存到 Users 而没有 person_id 来满足外键约束。在我的应用程序域中无法扭转这种 FK 关系,因为我们需要向左的一对多关系(用户 -> 1 人)。

我怀疑在 JSON 负载的 persons 对象中发送一个 id 可以正确保存。但是,由于各种原因,这在运行时是不可能的。例如,这是它在 "Saving Data" CakePHP Book page...

中的显示方式
$data = [
  'title' => 'First Post',
  'user' => [
      'id' => 1,
      'username' => 'mark'
  ]
];

...
$article = $articles->newEntity($data, [
    'associated' => ['Users']
]);

$articles->save($article);

我知道对于类似的问题,下面的也可能像 一样工作,因为 Persons 可以保存到 Users,但与我的相比感觉不太合适'我正在尝试做,感觉好像有一种方法可以通过 Users.

上的关联
  if ($this->request->is('post') && !empty($this->request->getData())) {
    $data = $this->request->getData();
    $newEntity = $this->Users->Persons->newEntity($data, ['associated' => 'Persons']);

    $savedEntity =  $this->Users->Persons->save($newEntity);
  ...

工作原理

现在我相信这在 CakePHP 2.X 中曾经是可能的,正如 this answer by ndm 在一个类似的问题中所述,有人试图保存 belongsTo 关联的实体并且它是通过 belongsTo 实体在一个请求中包含父 hasOne 实体。

That's the expected behavior, saveAssociated() is not meant to save only the associated records, it will save the main record as well, so you should use saveAssociated() only, no need to manually set the foreign key, etc, CakePHP will do that automatically.

Controller

public function create() {
    if ($this->request->is('post') && !empty($this->request->data)):
        $this->CandidatesProblemReport->create();
        if ($this->CandidatesProblemReport->saveAssociated($this->request->data)):
            // ...
        endif;
    endif;
}

但是,在文档中,我无法在 Users 实体继承自的 Cake\ORM\Table 对象上找到或使用 saveAssociated() 方法。调用它会产生找不到方法的错误。此方法似乎仅存在于 Cake\ORM\Association 对象上,如 documentation 中所述。除非我遗漏了明显的东西,否则有没有办法使用它,或者它是否被 BelongsTo() 及其兄弟方法在内部使用?

记录/转储实体

使用Cake\Log\Log::error($newEntity);die(var_dump($newEntity));显示Users水合到对象中的有效负载数据,但我没有看到附加的Persons对象(见下文).

object(MyPlugin\Model\Entity\User)[299]
  public 'username' => string 'foo' (length=3)
  public 'password' => string 'bar' (length=3)
  public '[new]' => boolean true
  public '[accessible]' => 
    array (size=5)
      '*' => boolean false
      'person_id' => boolean true
      'username' => boolean true
      'password' => boolean true
      'person' => boolean true
  public '[dirty]' => 
    array (size=2)
      'username' => boolean true
      'password' => boolean true
  public '[original]' => 
    array (size=0)
      empty
  public '[virtual]' => 
    array (size=0)
      empty
  public '[hasErrors]' => boolean false
  public '[errors]' => 
    array (size=0)
      empty
  public '[invalid]' => 
    array (size=0)
      empty
  public '[repository]' => string 'MyPlugin.Users' (length=17) 

尝试 \Cake\Log\Log::error($savedEntity); 日志文件中没有显示任何内容。

save() 协会参数

我考虑的另一种解决方案是使用 save()$options['associated],如 documentation 所示(摘录如下)。如下所示将此设置为 true 后,错误仍然发生。

save( Cake\Datasource\EntityInterface $entity , array $options [] )

... associated: If true it will save 1st level associated entities as they are found in the passed $entity whenever the property defined for the association is marked as dirty. If an array, it will be interpreted as the list of associations to be saved. It is possible to provide different options for saving on associated table objects using this key by making the custom options the array value. If false no associated records will be saved. (default: true) ...

UsersController.php:

  if ($this->request->is('post') && !empty($this->request->getData())) {
    $data = $this->request->getData();
    $newEntity = $this->Users->newEntity($data, ['associated' => 'Persons']);

    $savedEntity =  $this->Users->save($newEntity, ['associated' => true]);
  ...

总结

如果不通过 PersonsController.php 并利用其 hasOne 关系,我的 UsersPersons 数据无法通过 UsersController.php

如果我遗漏了任何重要信息,或者您还有 questions/need 更多信息,请询问!我可能错过了一些明显的东西,但我很感激任何可能的 suggestions/solutions。

正如@ndm 指出的那样,错误在于发布的数据。根据文档的 "Saving Data: Saving BelongsTo Associations" 页面:

When saving belongsTo associations, the ORM expects a single nested entity named with the singular, underscored version of the association name.

发布的密钥 persons 应该是 person。同样,如果实体被命名为 PersonSnapshots,则水化到实体中的有效负载中的相关密钥需要是 person_snapshot.