You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

API Platform/Symfony使用纯标识符遇报错的求助

问题:API Platform中使用纯标识符替代IRI时遇到的错误

在API Platform中尝试用纯ID替代IRI(后续计划切换为UUID),按照官方文档实现了自定义Denormalizer,但始终报错:Unable to generate an IRI for the item of type "App\Entity\Address"。


当前可正常使用的POST请求体

{
  "name": "Test",
  "address": {
    "street": "Test",
    "zip": "11111",
    "city": "Test",
    "country": "Test",
    "type": "/api/address_types/1" //Address Type ID
  },
  "cover": "/api/medias/17", //Cover ID
  "type":  "/api/stores/1" // Store Type ID
}

期望使用的POST请求体(存在问题)

{
  "name": "Test",
  "address": {
    "street": "Test",
    "zip": "11111",
    "city": "Test",
    "country": "Test",
    "type": 1 //Address Type ID
  },
  "cover": 17, //Cover ID
  "type":  1 // Store Type ID
}

Store实体代码(省略Getter和Setter)

<?php
#[ORM\Entity(repositoryClass: StoreRepository::class)]
#[ApiResource(
    operations: [
        new GetCollection(normalizationContext: ['groups' => 'store_collection_read']),
        new Get(normalizationContext: ['groups' => 'store_item_read']),
        new Post(denormalizationContext: ['groups' => 'store:write']),
        new Put(denormalizationContext: ['groups' => 'store:write']),
        new Patch(denormalizationContext: ['groups' => 'store:write']),
        new Delete()
    ]
)]
class Store
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    #[Groups(['store_collection_read', 'store_item_read'])]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    #[Groups(['store_collection_read', 'store_item_read', 'store:write'])]
    private ?string $name = null;

    #[ORM\Column(length: 255, nullable: true)]
    #[Groups(['store_item_read', 'store:write'])]
    private ?string $description = null;

    #[ORM\OneToOne(cascade: ['persist', 'remove', 'refresh'])]
    #[Groups(['store_collection_read', 'store_item_read', 'store:write'])]
    private ?Address $address = null;

    #[ORM\ManyToOne(targetEntity: Media::class)]
    #[ORM\JoinColumn(nullable: true)]
    #[ApiProperty(types: ['https://schema.org/image'])]
    #[Groups(['store_collection_read', 'store_item_read', 'store:write'])]
    public ?Media $cover = null;

    #[ORM\ManyToOne]
    #[Groups(['store_item_read', 'store:write', 'refresh'])]
    private ?StoreType $type = null;
}

Address实体代码(省略Getter和Setter)

class Address
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    #[Groups(['address:read', 'store_collection_read', 'store_item_read', 'store:write'])]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    #[Groups(['address:read', 'address:write', 'store_collection_read', 'store_item_read', 'store:write'])]
    private ?string $street = null;

    #[ORM\Column(length: 255, nullable: true)]
    #[Groups(['address:read', 'address:write', 'store_collection_read', 'store_item_read', 'store:write'])]
    private ?string $additionalAddress = null;

    #[ORM\Column(length: 100)]
    #[Groups(['address:read', 'address:write', 'store_collection_read', 'store_item_read', 'store:write'])]
    private ?string $zip = null;

    #[ORM\Column(length: 100)]
    #[Groups(['address:read', 'address:write', 'store_collection_read', 'store_item_read', 'store:write'])]
    private ?string $city = null;

    #[ORM\Column(length: 100)]
    #[Groups(['address:read', 'address:write', 'store_collection_read', 'store_item_read', 'store:write'])]
    private ?string $country = null;

    #[ORM\ManyToOne(inversedBy: 'addresses')]
    #[Groups(['address:read', 'address:write', 'store_item_read', 'store:write'])]
    private ?AddressType $type = null;
}

自定义PlainIdentifierDenormalizer代码

class PlainIdentifierDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface
{
    use DenormalizerAwareTrait;

    private IriConverterInterface $iriConverter;
    private LoggerInterface $logger;

    public function __construct(IriConverterInterface $iriConverter, LoggerInterface $logger)
    {
        $this->iriConverter = $iriConverter;
        $this->logger = $logger;
    }

    public function denormalize($data, $class, $format = null, array $context = []): mixed
    {
        if (isset($data['address']['type'])) {

            $data['address']['type'] =
                $this
                    ->iriConverter
                    ->getIriFromResource(
                        resource: Address::class,
                        context: ['uri_variables' => ['id' => $data['address']['type']]]
                    );
        }

        if (isset($data['cover'])) {
            $data['cover'] =
                $this
                    ->iriConverter
                    ->getIriFromResource(
                        resource: Media::class,
                        context: ['uri_variables' => ['id' => $data['cover']]]
                    );

        }

        if (isset($data['type'])) {
            $data['type'] =
                $this
                    ->iriConverter
                    ->getIriFromResource(
                        resource: StoreType::class,
                        context: ['uri_variables' => ['id' => $data['type']]]
                    );
        }

        return $this->denormalizer->denormalize($data, $class, $format, $context + [__CLASS__ => true]);
    }

    public function supportsDenormalization($data, $type, $format = null, array $context = []): bool
    {
        return \in_array($format, ['json', 'jsonld'], true) && is_a($type, Store::class, true) && !isset($context[__CLASS__]);
    }

    public function getSupportedTypes(?string $format): array
    {
        return [
            'object' => null,
            '*' => false,
            Store::class => true
        ];
    }
}

问题分析与修复方案

错误原因

自定义Denormalizer处理address.type时,错误传入Address::class作为资源类型,实际应该传入AddressType::class。这导致IriConverter尝试用AddressType的ID为Address实体生成IRI,触发异常。

修复后的核心代码

修改denormalize方法中处理address.type的部分:

if (isset($data['address']['type'])) {
    $data['address']['type'] =
        $this
            ->iriConverter
            ->getIriFromResource(
                resource: AddressType::class, // 替换为正确的实体类
                context: ['uri_variables' => ['id' => $data['address']['type']]]
            );
}

额外优化建议

  1. 避免硬编码字段和实体类,可通过反射或配置动态处理关联字段,提升代码可维护性。
  2. 增加ID有效性校验,避免无效值引发异常。
  3. 后续切换UUID时,只需修改实体ID字段类型为UUID,无需大幅调整Denormalizer逻辑。

内容的提问来源于stack exchange,提问作者user24954048

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 13:08:14