Могу ли я получить IRI нарушенной записи с помощью платформы API? - PullRequest
0 голосов
/ 13 июля 2020

У меня есть проект Symfony 5, использующий symfony/api (платформа API) с сущностью с ограничением 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], потому что [3,2] уже используется с идентификатором 1:

{
  "@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, или, по крайней мере, идентификатор элемента, который я хочу исправить.

EDIT:

Я видел в 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 …}
    }
  ]
}

Ответы [ 2 ]

1 голос
/ 14 июля 2020

В этом случае я всегда использую фильтры 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 из ответа, вы можете получить iri из "hydra:member"[0]

0 голосов
/ 19 июля 2020

Вам нужно создать свой собственный @ Assert , потому что это единственный способ для вас использовать две службы, которые обрабатывают это задание:

  1. CartItemRepository ,
  2. IriConverterInterface

Сначала создайте класс ограничения MyUniqueCartItem . Объявите его использование классу, чтобы вы могли получить доступ ко всем свойствам во время проверки:

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 общее c ограничение MyUniqueEntity путем добавления к нему некоторых свойств, в которых вы указываете необходимые критерии и репозиторий. Внутри MyUniqueEntityValidator замените CartItemRepository на EntityManagerInterface и выберите репозиторий:

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