我在使用基于PHP8.1的API平台时遇到了一个问题(v5.4)。
我有一个名为Customer的实体,它有email,phoneNumber字段,每个tenantId都是唯一的(每个惟一的db索引有两个字段)。
Doctrine映射(XML) re的一部分。对于这些索引:
<unique-constraints>
<unique-constraint columns="tenant_id,email" name="unique_customer_email"/>
<unique-constraint columns="tenant_id,phone_number" name="unique_customer_phone_number"/>
</unique-constraints>此外,该实体具有UniqueEntity注释(use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;),如下所示
<?php
declare(strict_types=1);
namespace App\Modules\RentalCustomers\Application\Query;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use App\Shared\Address\Address;
use App\Shared\Email\Email;
use App\Shared\Email\Serializer\EmailNormalizer;
use App\Shared\TaxNumber\TaxNumber;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use libphonenumber\PhoneNumber;
use Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber as AssertPhoneNumber;
use Ramsey\Uuid\UuidInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Serializer\Annotation\Context;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
#[ApiResource(
collectionOperations: [
'get' => [
'security_get_denormalize' => "is_granted('CUSTOMER_VIEW', object)",
],
'post' => [
'security_post_denormalize' => "is_granted('CUSTOMER_CREATE', object)",
],
],
itemOperations: [
'get' => [
'normalization_context' => [
'groups' => [
'customer:read',
'with:address',
],
],
'security' => "is_granted('CUSTOMER_VIEW', object)",
],
'put' => [
'normalization_context' => [
'groups' => [
'customers:write',
'with:address',
],
],
'security' => "is_granted('CUSTOMER_UPDATE', object)",
],
],
attributes: [
'validation_groups' => [self::class, 'validationGroups'],
],
denormalizationContext: [
'groups' => [
'customers:write',
'with:address',
],
],
normalizationContext: [
'groups' => [
'customers:read',
'with:address',
],
],
routePrefix: '/rental-customers',
)]
#[UniqueEntity(['tenantId', 'email'], null, null, null, null, null, 'email')]
#[UniqueEntity(['tenantId', 'phoneNumber'], null, null, null, null, null, 'phoneNumber')]
#[ApiFilter(SearchFilter::class, properties: ['lastName' => 'partial', 'email' => 'partial', 'phoneNumber' => 'partial'])]
class Customer
{
#[ApiProperty(writable: false, identifier: true)]
#[Groups(['customer:read', 'customers:read', 'customers:write'])]
public UuidInterface $id;
#[ApiProperty(writable: true, required: true)]
#[Assert\NotBlank(groups: ['company', 'Default'])]
#[Groups(['customer:read', 'customers:read', 'customers:write'])]
public ?string $companyName;
#[Assert\NotBlank(groups: ['company', 'Default'])]
#[Groups(['customer:read', 'customers:read', 'customers:write'])]
#[ApiProperty(
writable: true,
required: true,
attributes: [
'openapi_context' => [
'type' => 'string',
'example' => '542453244242',
],
]
)]
public ?TaxNumber $companyTaxNumber;
#[ApiProperty(required: true)]
#[Groups(['customer:read', 'customers:read', 'customers:write'])]
#[Assert\NotBlank(groups: ['person', 'Default'])]
public string $firstName;
#[ApiProperty(required: true)]
#[Groups(['customer:read', 'customers:read', 'customers:write'])]
#[Assert\NotBlank(groups: ['person', 'Default'])]
public string $lastName;
/**
* @AssertPhoneNumber
*/
#[ApiProperty(
attributes: [
'openapi_context' => [
'type' => 'string',
'example' => '+48500600700',
],
]
)]
#[Assert\NotBlank]
#[Groups(['customer:read', 'customers:read', 'customers:write'])]
public PhoneNumber $phoneNumber;
#[ApiProperty(
required: true,
attributes: [
'openapi_context' => [
'type' => 'string',
'example' => 'api@renthelp.io',
],
]
)]
#[Assert\Email, Assert\NotBlank]
#[Context([EmailNormalizer::class])]
#[Groups(['customer:read', 'customers:read', 'customers:write'])]
public Email $email;
#[Assert\NotNull(groups: ['person', 'Default']), Assert\NotBlank(groups: ['person', 'Default'])]
#[Groups(['customer:read', 'customers:read', 'customers:write'])]
public string $nationalIdentificationNumber;
#[ApiSubresource]
#[Groups(['customer:read', 'customers:read'])]
public Collection $documents;
#[Assert\Valid]
#[Groups(['customer:read', 'customers:write', 'customers:read'])]
#[ORM\Embedded(Address::class, 'address_')]
public Address $address;
#[ApiProperty(readable: false, writable: false)]
public UuidInterface $tenantId;
#[ApiProperty(writable: false)]
#[Groups(['customer:read', 'customers:read', 'customers:write'])]
public DateTimeImmutable $createdAt;
#[Assert\NotBlank]
#[Groups(['customer:read', 'customers:read', 'customers:write'])]
#[ApiProperty(
writable: true,
required: true,
attributes: [
'openapi_context' => [
'type' => 'string',
'example' => 'person or company',
],
]
)]
public CustomerType $type;
public function __construct()
{
$this->documents = new ArrayCollection();
}
public function toArray(): array
{
return [
'email' => $this->email->value(),
'firstName' => $this->firstName,
'lastName' => $this->lastName,
'phoneNumber' => $this->phoneNumber->getRawInput(),
'address' => $this->address->toArray(),
'nationalIdentificationNumber' => $this->nationalIdentificationNumber,
'type' => (string) $this->type,
'companyName' => $this->companyName ?? '',
'companyTaxNumber' => $this->companyTaxNumber !== null ? (string) $this->companyTaxNumber : '',
];
}
public static function validationGroups(self $customer): array
{
if ($customer->type->isCompany()) {
return ['Default', 'company'];
}
return ['Default', 'person'];
}
}还有..。Symfony没有捕获违反约束的行为。只有当我在一个列上索引时,它才能工作,而不是对更多的列(比如这里,两个)。端点返回一个异常(基于PostgreSQL),而不是422状态代码,其中包含与字段相关的冲突错误:
Uncaught PHP Exception Doctrine\DBAL\Exception\UniqueConstraintViolationException: "An exception occurred while executing a query: SQLSTATE[23505]: Unique violation: 7 ERROR: duplicate
key value violates unique constraint "unique_customer_email" DETAIL: Key (tenant_id, email)=(99ca30b3-56e6-4177-87a1-f5bd6e956ea4, test@test.com) already exists."有什么想法吗?我连续搜索了几次这个问题,但我还是在这里.
值得补充的是,我有一些对象用于这些字段,如:PhoneNumber、Email和TenantId,它们不是标量!但是,对于单个字段,它可以工作,所以我认为这不是问题所在。
我试着使用这些字段的不同顺序。我也是想找errorPath,但那是另一回事.:(
发布于 2022-03-30 22:50:00
可能不是一个完整的解决方案,但考虑一下可能会有帮助:
UniqueEntity和教条独特的约束是不同的,它们之间没有任何关系。
UniqueEntity是一个验证器,它依赖于实体存储库试图获取所有符合条件的实体。如果没有找到与标准匹配的实体(或者只有实际被验证的实体)验证,那么如果实际找到具有给定条件的不同实体,则验证将失败。从医生那里:
repositoryMethod:--用于确定唯一性的存储库方法的名称。如果留空,将使用findBy()。该方法作为其参数接收fieldName =>值关联数组(其中fieldName是字段选项中配置的每个字段)。https://symfony.com/doc/current/reference/constraints/UniqueEntity.html
另一方面,唯一的约束是在数据库表中创建真正的唯一索引。所以你才会得到你提到的例外。
我建议你直接进去
Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator::validate($entity, Constraint $constraint)
特别是第137行:
$result = $repository->{$constraint->repositoryMethod}($criteria)
查看为什么存储库无法获取冲突的实体。
https://stackoverflow.com/questions/71593257
复制相似问题