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']]] ); }
额外优化建议
- 避免硬编码字段和实体类,可通过反射或配置动态处理关联字段,提升代码可维护性。
- 增加ID有效性校验,避免无效值引发异常。
- 后续切换UUID时,只需修改实体ID字段类型为UUID,无需大幅调整Denormalizer逻辑。
内容的提问来源于stack exchange,提问作者user24954048
相关产品推荐
相关产品推荐

