[GraphQL] Nested input with an `id` creates a new object instead of updating the existing one (regression in 4.4)
维护者通常 3 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 新手友好度
- 45/100
调研方向
Start with the GraphQL denormalizer class ApiPlatform\GraphQl\Serializer\ItemDenormalizer, which extends AbstractItemNormalizer without the trait, and compare it with the generic ApiPlatform\Serializer\ItemDenormalizer and ItemNormalizerTrait::denormalize(), which handles a nested id through OBJECT_TO_POPULATE. Check MutationTest for existing nested-input coverage. Done when a GraphQL update mutation with a nested id updates the existing child instead of creating a new one, with a functional test covering it.
由索引模型根据 Issue 内容生成。
描述
API Platform version(s) affected: 4.4.0 – 4.4.6, 5.0.5 (works in 4.3.21)
Description
A GraphQL update mutation that sends a nested (writable-link) relation with its id no longer updates the referenced object. API Platform instead denormalizes the nested input into a new object and ignores the id. For a to-many relation with orphanRemoval, this deletes the existing row and inserts a new one with a different identifier. If the new object misses required fields, the flush fails with a not-null violation.
This worked up to 4.3.x. There, the GraphQL ItemNormalizer extended ApiPlatform\Serializer\ItemNormalizer, whose denormalize() turned a nested id into OBJECT_TO_POPULATE. Since the normalizer/denormalizer split in #7713, GraphQL denormalization goes through ApiPlatform\GraphQl\Serializer\ItemDenormalizer. That class extends AbstractItemNormalizer directly and does not use ApiPlatform\Serializer\ItemNormalizerTrait, which still contains that logic (denormalize() / updateObjectToPopulate()). The REST and JSON-LD denormalizers still use their traits.
The 4.4.0 changelog lists #7713 only as a refactoring, without a behaviour change. The serialization docs ("Embedded relations": an embedded resource with an @id is retrieved and updated, one without is created) describe the same rule for JSON-LD. GraphQL followed it with id up to 4.3. I could not find a GraphQL functional test that sends a nested id on an update mutation. MutationTest only covers nested inputs without an id, which are created.
How to reproduce
Fresh symfony/skeleton 8.1, api-platform/symfony, api-platform/doctrine-orm and api-platform/graphql (all split packages pinned to the same version), SQLite, api_platform.graphql.enabled: true.
#[ORM\Entity]
#[ApiResource(normalizationContext: ['groups' => ['owner:read']], denormalizationContext: ['groups' => ['owner:write']])]
class Owner
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column] #[Groups(['owner:read'])]
private ?int $id = null;
#[ORM\Column] #[Groups(['owner:read', 'owner:write'])]
private string $name = '';
#[ORM\OneToMany(mappedBy: 'owner', targetEntity: Child::class, cascade: ['persist'], orphanRemoval: true)]
#[Groups(['owner:read', 'owner:write'])]
private Collection $children;
// constructor, getters, addChild()/removeChild() maintaining Child::$owner
}
#[ORM\Entity]
#[ApiResource]
class Child
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column] #[Groups(['owner:read'])]
private ?int $id = null;
#[ORM\Column] #[Groups(['owner:read', 'owner:write'])]
private string $name = '';
#[ORM\ManyToOne(inversedBy: 'children')]
private ?Owner $owner = null;
// getters/setters
}
Seed one Owner with one Child (/api/children/1, name before), then run:
mutation($input: updateOwnerInput!) {
updateOwner(input: $input) { owner { id children { edges { node { id name } } } } }
}
{ "input": { "id": "/api/owners/1", "children": [ { "id": "/api/children/1", "name": "after" } ] } }
Result (same app; only the API Platform version changes):
| version | response child | child table after the mutation |
|---|---|---|
| 4.3.21 | /api/children/1, after |
[{"id":1,"name":"after","owner_id":1}] |
| 4.4.6 | /api/children/2, after |
[{"id":2,"name":"after","owner_id":1}] |
| 5.0.5 | /api/children/2, after |
[{"id":2,"name":"after","owner_id":1}] |
Expected: the existing child /api/children/1 is updated, as in 4.3.
Possible Solution
Let ApiPlatform\GraphQl\Serializer\ItemDenormalizer use ApiPlatform\Serializer\ItemNormalizerTrait, as the generic ApiPlatform\Serializer\ItemDenormalizer does, so that a nested id is resolved through the IRI converter into OBJECT_TO_POPULATE again, including the api_allow_update check. A functional test that updates a nested relation by id in a GraphQL mutation would cover it.
Additional Context
Workaround: decorate api_platform.graphql.denormalizer.item and set OBJECT_TO_POPULATE from the nested id before delegating, mirroring ItemNormalizerTrait::denormalize().
Environment of the reproduction: PHP 8.4.27 (CLI), macOS 26.6, Symfony 8.1.10, Doctrine ORM 3.7.6, webonyx/graphql-php 15.39.0, SQLite. Only the api-platform/* versions differ between the three runs.
- 主要语言
- PHP
- 星标
- 2.6k
- 派生
- 988
- 平均合并
- 4 天 13 小时
- 30 天内合并 PR
- 96
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
api-platform/core 的其他 Issue
-
API Platform ignores array shapes in the JSON Schema可能已有人在做 @afraca 于 10 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 78/100
api-platform/core#8766 ·
维护者通常 3 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
api-platform/core#8744 ·
维护者通常 3 天内回复
-
JSON:API: resource schemas (`*.jsonapi`) have `properties` but no `type: object`可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭
难度 2/5 1 小时以内 新手友好度 80/100
api-platform/core#8701 ·
维护者通常 3 天内回复
-
难度 5/5 一周以上 新手友好度 30/100
api-platform/core#8791 ·
维护者通常 3 天内回复
-
难度 4/5 3-5 天 新手友好度 48/100
api-platform/core#8790 ·
维护者通常 3 天内回复
查看 api-platform/core 的全部 Issue
相似的 Issue
-
Author mapping step: "create new user" and "assign to existing user" fields have no spacing between them可能已有人在做 @umeshnevase 今天认领。 未关闭
难度 1/5 1-3 小时 新手友好度 78/100
WordPress/wordpress-importer#275 ·
-
security/openvpn-legacy: read-only user can stop a running VPN可能已有人在做 @besendorf 今天认领。 未关闭incomplete
难度 2/5 1-3 小时 新手友好度 72/100
opnsense/plugins#5887 · 1 条评论 ·
维护者通常 1 天内回复
-
jira-created
难度 2/5 1-3 小时 新手友好度 77/100
-
No Code Attached Yet
难度 1/5 1 小时以内 新手友好度 90/100
joomla/joomla-cms#48786 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 76/100
Icinga/icingaweb2-module-director#3188 ·
维护者通常 4 天内回复