首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >有两个(许多)字段的UniqueEntity不能工作

有两个(许多)字段的UniqueEntity不能工作
EN

Stack Overflow用户
提问于 2022-03-23 19:53:47
回答 1查看 452关注 0票数 2

我在使用基于PHP8.1的API平台时遇到了一个问题(v5.4)。

我有一个名为Customer的实体,它有emailphoneNumber字段,每个tenantId都是唯一的(每个惟一的db索引有两个字段)。

Doctrine映射(XML) re的一部分。对于这些索引:

代码语言:javascript
复制
<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;),如下所示

代码语言:javascript
复制
<?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状态代码,其中包含与字段相关的冲突错误:

代码语言:javascript
复制
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."

有什么想法吗?我连续搜索了几次这个问题,但我还是在这里.

值得补充的是,我有一些对象用于这些字段,如:PhoneNumberEmailTenantId,它们不是标量!但是,对于单个字段,它可以工作,所以我认为这不是问题所在。

我试着使用这些字段的不同顺序。我也是想找errorPath,但那是另一回事.:(

EN

回答 1

Stack Overflow用户

发布于 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)

查看为什么存储库无法获取冲突的实体。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71593257

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档