我可以通过 API 平台获取违规条目的 IRI 吗?

Can I get the violated entry's IRI with API Platform?

我有一个使用 symfony/api(API 平台)的 Symfony 5 项目和一个带有 UniqueEntity 约束的实体(见下文。我省略了字段注释以便更好地理解它们与此处无关。):

/**
 * @ORM\Entity(repositoryClass=CartItemRepository::class)
 * @ApiResource(
 *     collectionOperations={"get","post"},
 *     itemOperations={"get","patch"},
 *     normalizationContext={"groups"={"cart_item:read"}}
 * )
 * @UniqueEntity(fields={"product","cart"})
 */
class CartItem
{
    private $id;
    private $product;
    private $quantity;
    private $cart;

    /* ... */
}

假设我有一些数据:

----------------------------------------
| id | product_id | cart_id | quantity |
|  1 |          3 |       2 |        1 |
|  2 |          2 |       2 |        2 |
----------------------------------------

现在,如果我发送此 POST 请求:

{
  "product": "/api/products/3",
  "quantity": 1,
  "cart": "/api/carts/2"
}

我当然会期待此 400 响应,因为插入此条目会破坏 [product_id,cart_id] 的唯一性,因为 ID 1 已使用 [3,2] :

{
  "@context": "/api/contexts/ConstraintViolationList",
  "@type": "ConstraintViolationList",
  "hydra:title": "An error occurred",
  "hydra:description": "product: This value is already used.",
  "violations": [
    {
      "propertyPath": "product",
      "message": "This value is already used."
    }
  ]
}

我的问题是:我是否有办法获取本应“重复”的实体的 IRI 或 ID?(在这里,ID 应该是为 1,则 IRI 为 /api/cart_items/1)。我想用这个实现的是重现 MySQL 的 ON DUPLICATE KEY UPDATE quantity = quantity + :quantity 行为,方法是检测此错误并在发生时发送 PATCH 请求,但发送 PATCH 请求,我需要 IRI,或者至少需要我要修补的项目的 ID。

编辑:

我在 Profiler 中看到它是可见的(请参阅下面的 cause 字段)但我无法进入 API 的响应...

Symfony\Component\Validator\ConstraintViolation {#2332 ▼
  -message: "This value is already used."
  -messageTemplate: "This value is already used."
  -parameters: [▶]
  -plural: null
  -root: App\Entity\CartItem {#675 ▼
    -id: null
    -product: App\Entity\Product {#1888 …}
    -quantity: 1
    -cart: App\Entity\Cart {#2119 …}
  }
  -propertyPath: "product"
  -invalidValue: App\Entity\Product {#1888 …}
  -constraint: Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity {#2130 …}
  -code: "23bd9dbf-6b9b-41cd-a99e-4844bcf3077f"
  -cause: [▼
    App\Entity\CartItem {#2330 ▼
      -id: 2
      -product: App\Entity\Product {#1888 …}
      -quantity: 2
      -cart: App\Entity\Cart {#2119 …}
    }
  ]
}

在这种情况下,我总是使用 API 过滤器 (https://api-platform.com/docs/core/filters/)。在发送 POST 请求之前,发送 GET 请求以检查实体是否存在。例如,为过滤器添加注释:

use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;

/**
 * @ORM\Entity(repositoryClass=CartItemRepository::class)
 * @ApiResource(
 *     collectionOperations={"get","post"},
 *     itemOperations={"get","patch"},
 *     normalizationContext={"groups"={"cart_item:read"}}
 * )
 * @UniqueEntity(fields={"product","cart"})
 *
 * @ApiFilter(SearchFilter::class, properties={"product": "exact", "cart": "exact"})
 */
class CartItem
{
    private $id;
    private $product;
    private $quantity;
    private $cart;

    /* ... */
}

并使用 URL 获取现有实体,如下所示:GET /cart_items?product=3&cart=2 您应该获取一个集合。因此,如果 "hydra:totalItems" > 0 来自响应,您可以从 "hydra:member"[0]

检索 iri

您需要 create your custom @Assert, because it is the only way 才能使用处理该作业的两项服务:

  1. CartItemRepository,
  2. IriConverterInterface

首先,创建约束 class MyUniqueCartItem。向 class 声明其用法,以便您可以在验证期间访问所有属性:

public function getTargets()
{
    return self::CLASS_CONSTRAINT;
}

然后,创建 MyUniqueCartItemValidator,并将之前的两个服务自动连接到它。使用 CartItemRepository 检查实体是否已经存在,并使用 IriConverterInterface 检索 IRI:

public function validate($value, Constraint $constraint)
{
    // i make it short, just take look ath  the doc
    $criteria = ['cart' => $value->getCart(), 'product' => $value->getProduct()]
    $duplicated = $this->cartItemRepository->findOneBy($criteria);
    if ($duplicated !== null) {
        $iri = $this->iriConverterInterface->getIriFromItem($duplicated);
        $this->context->buildViolation("Duplicated entity : $iri" )
                ->atPath('whatever')
                ->addViolation();
    }
}

请注意,您可以通过向其添加一些属性来使 MyUniqueCartItem 成为通用约束 MyUniqueEntity存储库。在 MyUniqueEntityValidator 中,将 CartItemRepository 替换为 EntityManagerInterface 并获取存储库:

$repository = $this->entityManger->getRepository($constraint->entityClass);