Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

JSON:API: relations set through the constructor reject spec resource linkage (`relationships.<name>.data`)

未关闭
#8,702 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
3/5
预计耗时
1-2 天
新手友好度
68/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
php, symfony
领域
api

调研方向

Start with JsonApi\Serializer\ItemNormalizerTrait::setAttributeValue(), which strips the data wrapper, and compare it with AbstractItemNormalizer::createConstructorArgument(), which calls createAndValidateAttributeValue() without that step. Then check denormalizeRelation() in the JSON:API serializer. Done when a constructor-argument relation accepts relationships.<name>.data for to-one (including null) and to-many, and the flat shape is rejected, with a test modelled on the Book/Author fixture.

由索引模型根据 Issue 内容生成。

描述

API Platform version(s) affected

4.4.4

Description

When a relation is set through a constructor argument, the JSON:API denormalizer does not accept the resource linkage shape of the spec (relationships.<name>.data).

  • To-one with {"data": {"type": ..., "id": ...}} or {"data": null}: 400 "Only resource linkage supported currently".
  • To-many with {"data": [...]}: 400 "The type of the key "data" must be "int", "string" given."
  • To-one with the flat shape {"type": ..., "id": ...}, which is not the spec shape: 201.

The same relation as a public property (no constructor) accepts the spec shape.

The cause looks like this: JsonApi\Serializer\ItemNormalizerTrait::setAttributeValue() removes the data wrapper, but AbstractItemNormalizer::createConstructorArgument() calls createAndValidateAttributeValue() directly. The JSON:API normalizer does not override it, so the value still has the data wrapper when it gets to denormalizeRelation().

How to reproduce
#[ApiResource(operations: [new Get(provider: [self::class, 'provide'])])]
final class Author
{
    public function __construct(public int $id = 1, public string $name = 'Frank Herbert') {}
    public static function provide(): self { return new self(); }
}

#[ApiResource(operations: [
    new Get(provider: [self::class, 'provide']),
    new Post(processor: [self::class, 'process']),
])]
final class Book
{
    /** @param list<Author> $coAuthors */
    public function __construct(
        public int $id = 1,
        public string $title = 'Dune',
        public ?Author $author = null,
        public array $coAuthors = [],
    ) {}

    public static function provide(): ?self { return null; }
    public static function process(self $data, Operation $operation): self { return $data; }
}

// Same relation, set as a property
#[ApiResource(operations: [
    new Get(provider: [self::class, 'provide']),
    new Post(processor: [self::class, 'process']),
])]
final class Novel
{
    public int $id = 1;
    public string $title = 'Dune';
    public ?Author $author = null;

    public static function provide(): self { return new self(); }
    public static function process(self $data, Operation $operation): self { return $data; }
}
POST relationships Status
/books {"author":{"data":{"type":"Author","id":"/authors/1"}}} 400
/books {"author":{"data":null}} 400
/books {"coAuthors":{"data":[{"type":"Author","id":"/authors/1"}]}} 400
/books {"author":{"type":"Author","id":"/authors/1"}} (not spec) 201
/novels {"author":{"data":{"type":"Author","id":"/authors/1"}}} 201

Full request for the first row:

POST /books

{"data":{"type":"Book","attributes":{"title":"A"},"relationships":{"author":{"data":{"type":"Author","id":"/authors/1"}}}}}
{"errors":[{"id":"/errors/400","title":"An error occurred","detail":"Only resource linkage supported currently, see: http://jsonapi.org/format/#document-resource-object-linkage.","status":400,"type":"/errors/400","links":{"type":"/errors/400"},"code":"400"}]}

Expected: a relation set through the constructor accepts the same relationships.<name>.data shape as a relation set through a property. The flat shape is not accepted.

Possible Solution

Remove the data wrapper for constructor arguments too, for example with a createConstructorArgument() override in ItemNormalizerTrait that does what setAttributeValue() does.

Additional Context

Checked on api-platform/core v4.4.4, Symfony 7.4, PHP 8.5, in an empty Symfony app. The related code in src/JsonApi/Serializer is the same on main, except for the error status cast (#8366).

The app has only FrameworkBundle, TwigBundle and ApiPlatformBundle, with this config:

$c->extension('api_platform', [
    'mapping' => ['paths' => ['%kernel.project_dir%/src/ApiResource']],
    'formats' => ['jsonapi' => ['application/vnd.api+json']],
    'error_formats' => ['jsonapi' => ['application/vnd.api+json'], 'jsonproblem' => ['application/problem+json']],
    'doctrine' => ['enabled' => false],
]);

All requests send Accept: application/vnd.api+json and, when they have a body, Content-Type: application/vnd.api+json. The responses are from the prod environment.

主要语言
PHP
星标
2.6k
派生
989
平均合并
1 天 23 小时
30 天内合并 PR
102

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

api-platform/core 的其他 Issue

查看 api-platform/core 的全部 Issue

相似的 Issue

更多 PHP Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。