belongsToMany:允许并重用具有现有唯一标题的关联条目
belongsToMany: allow and reuse an associated entry with existing UNIQUE title
我有什么
我的 belongsToMany 协会与 CakePHP Cookbook 的协会相似。但是,我在标签标题上设置了 UNIQUE
约束。
(另一个可能无关紧要的区别是,我在 Tags table 中的每个标签旁边添加了一个 site_id
字段,另一个在 tag
和 site_id
上都设置了复合 UNIQUE
约束。)
什么不起作用
提交重复的标签标题会导致错误。
当我在保存之前调试我的新 Article 实体时,我可以看到重复的标签标题在验证尝试后被拒绝了。
'tags' => [
// This submitted tag title already exists in Tags
(int) 0 => object(App\Model\Entity\Tag) id:1 {
'site_id' => (int) 2
'[new]' => true
'[accessible]' => [
'site_id' => true,
'title' => true,
'created' => true,
'modified' => true,
'site' => true,
'articles' => true,
]
'[dirty]' => [
'site_id' => true,
]
'[original]' => [
]
'[virtual]' => [
]
'[hasErrors]' => true
'[errors]' => [
'title' => [
'unique' => 'The provided value is invalid', // ← error
],
]
'[invalid]' => [
'title' => 'test',
]
'[repository]' => 'Tags'
},
// …
// This submitted tag title does *not* already exist in Tags
(int) 3 => object(App\Model\Entity\Tag) id:4 {
'title' => 'tag'
'site_id' => (int) 2
'[new]' => true
'[accessible]' => [
'site_id' => true,
'title' => true,
'created' => true,
'modified' => true,
'site' => true,
'articles' => true,
]
'[dirty]' => [
'title' => true, // ← no error
'site_id' => true,
]
'[original]' => [
]
'[virtual]' => [
]
'[hasErrors]' => false
'[errors]' => [
]
'[invalid]' => [
]
'[repository]' => 'Tags'
},
]
我期望它如何工作?
我正在寻找的行为是,如果标签已经存在,则获取其 ID,然后 link 将提交的文章条目 link 到该现有 ID。所以 ON DUPLICATE KEY
子句,在某种程度上。
是否有一个我遗漏的标志 tell/allow ORM 可以执行此操作,或者我是否应该开始尝试一些 ->epilog()
技巧?
ORM 保存过程没有这样的功能,不,你不能使用默认 ORM save()
过程的 epilog()
,你必须实际创建插入查询然后手动,但是你不能真正使用实体然后它不会解决验证问题,你必须或多或少手动应用验证和应用程序规则(你不想盲目地将数据插入插入查询,甚至艰难 Query::values()
绑定数据)。
我可能会建议检查在编组之前修改数据的解决方案是否适合,这将透明地集成到流程中。您可以使用您的唯一索引列来查找现有的行,并将它们的主键值注入到请求数据中,然后 patching/marshalling 进程将能够正确地查找现有记录并相应地更新它们。
根据具体用例,这可能比手动构建插入查询需要更多工作,但恕我直言,它会集成得更好。在您的特定情况下,它可能更容易,因为使用手动插入查询需要您分别为所有不同的表插入数据,因为您不能将 ORM 的关联保存功能与手动构造的插入查询一起使用。
为了结束事情,这里有一些未经测试的快速和粗略的示例代码来说明这个概念:
// in ArticlesTable
public function beforeMarshal(
\Cake\Event\EventInterface $event,
\ArrayAccess $data,
\ArrayObject $options
): void {
// extract lookup keys from request data
$keys = collection($data['tags'])
->extract(function ($row) {
return [
$row['tag'],
$row['site_id'],
];
})
->toArray();
// query possibly existing rows based on the extracted lookup keys
$query = $this->Tags
->find()
->select(['id', 'tag', 'site_id'])
->where(
new \Cake\Database\Expression\TupleComparison(
['tag', 'site_id'],
$keys,
['string', 'integer'],
'IN'
)
)
->disableHydration();
// create a map of lookup keys and primary keys from the queried rows
$map = $query
->all()
->combine(
function ($row) {
return $row['tag'] . ';' . $row['site_id'];
},
'id'
)
->toArray();
// inject primary keys based on whether lookup keys exist in the map
$data['tags'] = collection($data['tags'])
->map(function ($row) use ($map) {
$key = $row['tag'] . ';' . $row['site_id'];
if (isset($map[$key])) {
$row['id'] = $map[$key];
}
return $row;
})
->toArray();
}
通过注入现有记录的主键,编组、验证、规则和保存应该能够正确区分要更新的内容和插入的内容,即您应该能够继续使用默认的 ORM 保存过程,就像你习惯了。
另见
我有什么
我的 belongsToMany 协会与 CakePHP Cookbook 的协会相似。但是,我在标签标题上设置了 UNIQUE
约束。
(另一个可能无关紧要的区别是,我在 Tags table 中的每个标签旁边添加了一个 site_id
字段,另一个在 tag
和 site_id
上都设置了复合 UNIQUE
约束。)
什么不起作用
提交重复的标签标题会导致错误。
当我在保存之前调试我的新 Article 实体时,我可以看到重复的标签标题在验证尝试后被拒绝了。
'tags' => [
// This submitted tag title already exists in Tags
(int) 0 => object(App\Model\Entity\Tag) id:1 {
'site_id' => (int) 2
'[new]' => true
'[accessible]' => [
'site_id' => true,
'title' => true,
'created' => true,
'modified' => true,
'site' => true,
'articles' => true,
]
'[dirty]' => [
'site_id' => true,
]
'[original]' => [
]
'[virtual]' => [
]
'[hasErrors]' => true
'[errors]' => [
'title' => [
'unique' => 'The provided value is invalid', // ← error
],
]
'[invalid]' => [
'title' => 'test',
]
'[repository]' => 'Tags'
},
// …
// This submitted tag title does *not* already exist in Tags
(int) 3 => object(App\Model\Entity\Tag) id:4 {
'title' => 'tag'
'site_id' => (int) 2
'[new]' => true
'[accessible]' => [
'site_id' => true,
'title' => true,
'created' => true,
'modified' => true,
'site' => true,
'articles' => true,
]
'[dirty]' => [
'title' => true, // ← no error
'site_id' => true,
]
'[original]' => [
]
'[virtual]' => [
]
'[hasErrors]' => false
'[errors]' => [
]
'[invalid]' => [
]
'[repository]' => 'Tags'
},
]
我期望它如何工作?
我正在寻找的行为是,如果标签已经存在,则获取其 ID,然后 link 将提交的文章条目 link 到该现有 ID。所以 ON DUPLICATE KEY
子句,在某种程度上。
是否有一个我遗漏的标志 tell/allow ORM 可以执行此操作,或者我是否应该开始尝试一些 ->epilog()
技巧?
ORM 保存过程没有这样的功能,不,你不能使用默认 ORM save()
过程的 epilog()
,你必须实际创建插入查询然后手动,但是你不能真正使用实体然后它不会解决验证问题,你必须或多或少手动应用验证和应用程序规则(你不想盲目地将数据插入插入查询,甚至艰难 Query::values()
绑定数据)。
我可能会建议检查在编组之前修改数据的解决方案是否适合,这将透明地集成到流程中。您可以使用您的唯一索引列来查找现有的行,并将它们的主键值注入到请求数据中,然后 patching/marshalling 进程将能够正确地查找现有记录并相应地更新它们。
根据具体用例,这可能比手动构建插入查询需要更多工作,但恕我直言,它会集成得更好。在您的特定情况下,它可能更容易,因为使用手动插入查询需要您分别为所有不同的表插入数据,因为您不能将 ORM 的关联保存功能与手动构造的插入查询一起使用。
为了结束事情,这里有一些未经测试的快速和粗略的示例代码来说明这个概念:
// in ArticlesTable
public function beforeMarshal(
\Cake\Event\EventInterface $event,
\ArrayAccess $data,
\ArrayObject $options
): void {
// extract lookup keys from request data
$keys = collection($data['tags'])
->extract(function ($row) {
return [
$row['tag'],
$row['site_id'],
];
})
->toArray();
// query possibly existing rows based on the extracted lookup keys
$query = $this->Tags
->find()
->select(['id', 'tag', 'site_id'])
->where(
new \Cake\Database\Expression\TupleComparison(
['tag', 'site_id'],
$keys,
['string', 'integer'],
'IN'
)
)
->disableHydration();
// create a map of lookup keys and primary keys from the queried rows
$map = $query
->all()
->combine(
function ($row) {
return $row['tag'] . ';' . $row['site_id'];
},
'id'
)
->toArray();
// inject primary keys based on whether lookup keys exist in the map
$data['tags'] = collection($data['tags'])
->map(function ($row) use ($map) {
$key = $row['tag'] . ';' . $row['site_id'];
if (isset($map[$key])) {
$row['id'] = $map[$key];
}
return $row;
})
->toArray();
}
通过注入现有记录的主键,编组、验证、规则和保存应该能够正确区分要更新的内容和插入的内容,即您应该能够继续使用默认的 ORM 保存过程,就像你习惯了。
另见