cakephp 属于多个:允许并重用具有现有UNIQUE标题关联条目

vddsk6oq  于 2022-11-11  发布在  PHP
关注(0)|答案(1)|浏览(147)

我拥有的
我的 belongsToMany 关联类似于CakePHP Cookbook中的关联,但是我在标记标题上设置了UNIQUE约束。
(另一个可能无关紧要的区别是,我在 Tags 表中的每个标签旁边添加了一个site_id字段,并且在tagsite_id上设置了另一个复合UNIQUE约束。)

不起作用的地方

提交重复的标记标题会导致错误。
当我在保存新的 Article 实体之前调试它时,我可以看到重复的标记标题在验证尝试后被拒绝。

  1. 'tags' => [
  2. // This submitted tag title already exists in Tags
  3. (int) 0 => object(App\Model\Entity\Tag) id:1 {
  4. 'site_id' => (int) 2
  5. '[new]' => true
  6. '[accessible]' => [
  7. 'site_id' => true,
  8. 'title' => true,
  9. 'created' => true,
  10. 'modified' => true,
  11. 'site' => true,
  12. 'articles' => true,
  13. ]
  14. '[dirty]' => [
  15. 'site_id' => true,
  16. ]
  17. '[original]' => [
  18. ]
  19. '[virtual]' => [
  20. ]
  21. '[hasErrors]' => true
  22. '[errors]' => [
  23. 'title' => [
  24. 'unique' => 'The provided value is invalid', // ← error
  25. ],
  26. ]
  27. '[invalid]' => [
  28. 'title' => 'test',
  29. ]
  30. '[repository]' => 'Tags'
  31. },
  32. // …
  33. // This submitted tag title does *not* already exist in Tags
  34. (int) 3 => object(App\Model\Entity\Tag) id:4 {
  35. 'title' => 'tag'
  36. 'site_id' => (int) 2
  37. '[new]' => true
  38. '[accessible]' => [
  39. 'site_id' => true,
  40. 'title' => true,
  41. 'created' => true,
  42. 'modified' => true,
  43. 'site' => true,
  44. 'articles' => true,
  45. ]
  46. '[dirty]' => [
  47. 'title' => true, // ← no error
  48. 'site_id' => true,
  49. ]
  50. '[original]' => [
  51. ]
  52. '[virtual]' => [
  53. ]
  54. '[hasErrors]' => false
  55. '[errors]' => [
  56. ]
  57. '[invalid]' => [
  58. ]
  59. '[repository]' => 'Tags'
  60. },
  61. ]

我期望它如何工作?

我所寻找的行为是,如果一个标签已经存在,那么就取它的ID,然后把提交的文章条目链接到这个现有的ID。
是否有一个标志,我错过了,将告诉/允许ORM这样做,或者我也许应该开始尝试一些->epilog()技巧?

gpnt7bae

gpnt7bae1#

ORM保存过程没有这样的功能,并且您不能将epilog()与默认的ORM save()过程一起使用,您必须实际手动创建插入查询,但是您不能真正使用实体,并且它也不能解决验证问题,和应用程序规则(您不希望盲目地将数据插入到插入查询中,即使Query::values()绑定数据也是如此)。
我可能会建议检查一下在编组之前修改数据的解决方案是否合适,它将透明地集成到过程中。您可以使用唯一索引列来查找现有的行,并将它们的主键值注入到请求数据中,然后修补/编组过程将能够正确地查找现有的记录并相应地更新它们。
根据具体的使用情况,这可能比手动构造插入查询要多做一些工作,但IMHO集成起来会更好。在您的特定情况下,这可能更容易,因为使用手动插入查询需要您分别为所有不同的表插入数据,因为您不能使用ORM的关联保存功能和手动构造的插入查询。
最后,这里有一些未经测试的快速和肮脏的示例代码来说明这个概念:

  1. // in ArticlesTable
  2. public function beforeMarshal(
  3. \Cake\Event\EventInterface $event,
  4. \ArrayAccess $data,
  5. \ArrayObject $options
  6. ): void {
  7. // extract lookup keys from request data
  8. $keys = collection($data['tags'])
  9. ->extract(function ($row) {
  10. return [
  11. $row['tag'],
  12. $row['site_id'],
  13. ];
  14. })
  15. ->toArray();
  16. // query possibly existing rows based on the extracted lookup keys
  17. $query = $this->Tags
  18. ->find()
  19. ->select(['id', 'tag', 'site_id'])
  20. ->where(
  21. new \Cake\Database\Expression\TupleComparison(
  22. ['tag', 'site_id'],
  23. $keys,
  24. ['string', 'integer'],
  25. 'IN'
  26. )
  27. )
  28. ->disableHydration();
  29. // create a map of lookup keys and primary keys from the queried rows
  30. $map = $query
  31. ->all()
  32. ->combine(
  33. function ($row) {
  34. return $row['tag'] . ';' . $row['site_id'];
  35. },
  36. 'id'
  37. )
  38. ->toArray();
  39. // inject primary keys based on whether lookup keys exist in the map
  40. $data['tags'] = collection($data['tags'])
  41. ->map(function ($row) use ($map) {
  42. $key = $row['tag'] . ';' . $row['site_id'];
  43. if (isset($map[$key])) {
  44. $row['id'] = $map[$key];
  45. }
  46. return $row;
  47. })
  48. ->toArray();
  49. }

通过注入现有记录的主键,编组、验证、规则和保存应该能够正确区分要更新的内容和要插入的内容,即,您应该能够继续使用默认的ORM保存过程,就像您习惯的那样。
另请参阅

*Cookbook〉数据库访问和ORM〉保存数据〉在构建实体之前修改请求数据
*Cookbook〉数据库访问和ORM〉查询生成器〉插入数据
*操作手册〉数据库访问和ORM〉表对象〉生命周期回调〉beforeMarshal

展开查看全部

相关问题