如何在API Platform中持久化多态集合(报价单场景)
解决方案:主端持久化OneToMany多态集合
问题核心
API Platform默认不会自动识别多态子类的实例化,而鉴别器字段type由Doctrine ORM自动维护,不能手动映射为实体属性——直接传type会被忽略,手动映射则会和ORM的鉴别器列冲突。更新操作正常是因为更新时ORM已经知晓实例的子类类型,无需重新判断。
优先方案:用JSON-LD的@type标识子类
这是API Platform处理多态关联的标准方式,无需自定义控制器或处理器:
确保实体鉴别器映射正确
在父类QuotationRow上配置单表继承的鉴别器映射,让ORM能正确关联子类与type值:/** * @ORM\Entity * @ORM\InheritanceType("SINGLE_TABLE") * @ORM\DiscriminatorColumn(name="type", type="string") * @ORM\DiscriminatorMap({ * "product" = QuotationRowProduct::class, * "text" = QuotationRowText::class, * "subtotal" = QuotationRowSubtotal::class * }) */ abstract class QuotationRow { // 父类属性、关联方法(如setQuotation) }注意:
QuotationRow需设为抽象类,子类各自标注@ORM\Entity。修改请求体结构
在quotationRows数组的每个元素中添加@type字段,值对应子类的类名(或鉴别器映射里的键),API Platform会自动实例化对应的子类,ORM也会自动设置type字段:{ "name": "客户报价单", "quotationRows": [ { "@type": "QuotationRowProduct", "productName": "XX型号服务器", "quantity": 1, "unitPrice": 8999 }, { "@type": "QuotationRowText", "content": "含一年免费上门服务" }, { "@type": "QuotationRowSubtotal", "label": "合计", "amount": 8999 } ] }配置序列化组
确保Quotation的quotationRows关联和子类的属性都配置了正确的序列化组(如quotation:write),让API Platform能正确反序列化请求数据:// Quotation类 /** * @ORM\OneToMany(targetEntity=QuotationRow::class, mappedBy="quotation", cascade={"persist", "remove"}, orphanRemoval=true) * @ApiSubresource * @ApiProperty(serializedGroups={"quotation:read", "quotation:write"}) */ private $quotationRows; // QuotationRowProduct类 /** * @ORM\Column(type="string") * @ApiProperty(serializedGroups={"quotation:read", "quotation:write"}) */ private $productName;
备选方案:自定义DataPersister处理子类实例化
如果前端无法传递JSON-LD的@type字段,可以自定义数据处理器,在持久化前手动替换父类实例为对应子类:
创建自定义DataPersister
namespace App\DataPersister; use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface; use App\Entity\Quotation; use App\Entity\QuotationRowProduct; use App\Entity\QuotationRowText; use App\Entity\QuotationRowSubtotal; use Doctrine\ORM\EntityManagerInterface; class QuotationDataPersister implements ContextAwareDataPersisterInterface { private $em; public function __construct(EntityManagerInterface $em) { $this->em = $em; } public function supports($data, array $context = []): bool { // 仅处理Quotation的POST请求 return $data instanceof Quotation && ($context['collection_operation_name'] ?? '') === 'post'; } public function persist($data, array $context = []) { $rows = $data->getQuotationRows(); $data->clearQuotationRows(); foreach ($rows as $row) { // 假设请求里传入了自定义的rowType字段(需在QuotationRow里添加非映射的getter/setter) $rowType = $row->getRowType(); $newRow = null; switch ($rowType) { case 'product': $newRow = new QuotationRowProduct(); $newRow->setProductName($row->getProductName()); $newRow->setQuantity($row->getQuantity()); $newRow->setUnitPrice($row->getUnitPrice()); break; case 'text': $newRow = new QuotationRowText(); $newRow->setContent($row->getContent()); break; case 'subtotal': $newRow = new QuotationRowSubtotal(); $newRow->setLabel($row->getLabel()); $newRow->setAmount($row->getAmount()); break; } if ($newRow) { $newRow->setQuotation($data); $data->addQuotationRow($newRow); } } $this->em->persist($data); $this->em->flush(); return $data; } public function remove($data, array $context = []) { $this->em->remove($data); $this->em->flush(); } }在QuotationRow添加临时属性
不需要ORM映射,仅用于接收请求里的类型标识:class QuotationRow { private $rowType; public function getRowType(): ?string { return $this->rowType; } public function setRowType(string $rowType): self { $this->rowType = $rowType; return $this; } }
总结
- 优先使用JSON-LD的
@type方案,符合API Platform和JSON-LD的规范,无需额外代码。 - 自定义DataPersister是备选方案,比直接写自定义控制器更符合API Platform的架构设计,避免重复处理路由、序列化等逻辑。
内容的提问来源于stack exchange,提问作者Jayss230
相关产品推荐
相关产品推荐

