You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用@UniqueEntity实现同一Perimeter下SiretNumber的唯一约束?

实现同一Perimeter下Supplier的siretNumber唯一校验

我需要实现的需求是:仅当同一Perimeter下已存在关联该siretNumber的Supplier时,才校验siretNumber的唯一性。也就是说不同Perimeter可以拥有相同siretNumber的Supplier,但同一Perimeter下的Supplier的siretNumber必须是唯一的。

现有Supplier实体代码

<?php

/**
* @ORM\Entity(repositoryClass=SupplierRepository::class)
* @UniqueEntity("siretNumber",repositoryMethod="getSupplierByPerimeter", message="Le numéro de SIRET est déjà utilisé.")
*/
class Supplier
{
   /**
    * @ORM\Id
    * @ORM\GeneratedValue
    * @ORM\Column(type="integer")
    */
   private $id;

   /**
    * @Assert\NotBlank(allowNull=true)
    * @Assert\Regex(pattern = "/^[0-9]*$", message="Seul les chiffres sont autorisés.")
    * @Assert\Length(
    *      min=14,
    *      max=14,
    *      exactMessage = "Le numéro de SIRET doit faire exactement 14 chiffres (SIREN (9) et NIC (5))"
    * )
    * @ORM\Column(type="string", length=14, nullable=true, unique=true)
    */
   private $siretNumber;

   /**
    * @ORM\ManyToOne(targetEntity=Perimeter::class, inversedBy="suppliers")
    * @ORM\JoinColumn(nullable=false)
    */
   private $perimeter;
}

尝试的仓库方法实现

我尝试使用@UniqueEntity的repositoryMethod参数,对应的仓库方法代码如下:

<?php

namespace App\Repository;

use App\Entity\Supplier;
use App\Repository\PerimeterRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Security;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;

/**
* @method Supplier|null find($id, $lockMode = null, $lockVersion = null)
* @method Supplier|null findOneBy(array $criteria, array $orderBy = null)
* @method Supplier[]    findAll()
* @method Supplier[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class SupplierRepository extends ServiceEntityRepository
{
   private $security;
   private $perimeterRepository;

   public function __construct(ManagerRegistry $registry, Security $security,PerimeterRepository $perimeterRepository)
   {
       parent::__construct($registry, Supplier::class);
       $this->security = $security;
       $this->perimeterRepository = $perimeterRepository;
   }

   /**
   * @return Supplier[] Returns an array of Supplier objects
   */
   public function getSupplierByPerimeter()
   {
       $perimeter = $this->perimeterRepository->findOneBy(["label" => $this->security->getUser()->getOptions()["perimeter"]]);

       return $this->createQueryBuilder('s')
           ->where('s.perimeter = :perimeter')
           ->setParameter('perimeter', $perimeter)
           ->getQuery()
           ->getResult()
       ;
   }
}

遇到的问题

实际运行时,@UniqueEntity仍在整个Supplier表中校验唯一性,而非仅校验getSupplierByPerimeter返回的结果集。因此即使siretNumber属于其他Perimeter,创建新Supplier时仍会触发校验错误。我可能未正确使用repositoryMethod,请问该如何实现需求?


问题根源与解决方案

核心问题分析

  1. 数据库层面全局唯一限制:实体类中siretNumber字段的@ORM\Column(unique=true)属性,会让Doctrine在数据库创建全局唯一索引,直接导致跨Perimeter的siretNumber无法重复,这是最根本的问题。
  2. repositoryMethod用法错误:该方法需要接收待校验的实体对象作为参数,根据实体自身的perimeter和siretNumber查询重复,而非返回当前用户所属Perimeter的所有Supplier。

正确实现步骤

方式一:使用@UniqueEntity联合字段校验(简洁方案)

直接通过@UniqueEntity指定联合字段,实现同一Perimeter下的siretNumber唯一:

/**
* @ORM\Entity(repositoryClass=SupplierRepository::class)
* @UniqueEntity(
*     fields={"siretNumber", "perimeter"},
*     message="Ce numéro de SIRET existe déjà dans ce périmètre."
* )
* @ORM\Table(uniqueConstraints={
*     @ORM\UniqueConstraint(name="idx_supplier_siret_perimeter", columns={"siret_number", "perimeter_id"})
* })
*/
class Supplier
{
   // ...其他字段保持不变

   /**
    * @Assert\NotBlank(allowNull=true)
    * @Assert\Regex(pattern = "/^[0-9]*$", message="Seul les chiffres sont autorisés.")
    * @Assert\Length(
    *      min=14,
    *      max=14,
    *      exactMessage = "Le numéro de SIRET doit faire exactement 14 chiffres (SIREN (9) et NIC (5))"
    * )
    * @ORM\Column(type="string", length=14, nullable=true) // 移除unique=true
    */
   private $siretNumber;

   // ...perimeter字段保持不变
}
  • 移除siretNumber的unique=true属性,避免全局唯一限制
  • 通过fields={"siretNumber", "perimeter"}指定联合校验规则
  • 推荐添加数据库层面的联合唯一索引(通过@ORM\Table注解),避免并发场景下的数据重复

方式二:正确使用repositoryMethod(复杂场景方案)

如果需要更复杂的校验逻辑,可自定义仓库方法:

  1. 修改实体注解:
/**
* @ORM\Entity(repositoryClass=SupplierRepository::class)
* @UniqueEntity(
*     fields="siretNumber",
*     repositoryMethod="findExistingSupplierByPerimeterAndSiret",
*     message="Ce numéro de SIRET existe déjà dans ce périmètre."
* )
*/
class Supplier
{
   // ...移除siretNumber的unique=true,其他字段不变
}
  1. 修改仓库方法,接收待校验实体并查询重复:
/**
 * 检查同一Perimeter下是否存在相同siretNumber的Supplier
 * @param Supplier $supplier
 * @return Supplier|null
 */
public function findExistingSupplierByPerimeterAndSiret(Supplier $supplier): ?Supplier
{
    $qb = $this->createQueryBuilder('s')
        ->where('s.siretNumber = :siret')
        ->andWhere('s.perimeter = :perimeter')
        ->setParameter('siret', $supplier->getSiretNumber())
        ->setParameter('perimeter', $supplier->getPerimeter());

    // 编辑场景下排除当前实体自身
    if ($supplier->getId()) {
        $qb->andWhere('s.id != :id')
           ->setParameter('id', $supplier->getId());
    }

    return $qb->getQuery()->getOneOrNullResult();
}

内容的提问来源于stack exchange,提问作者Laura Leroy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 21:44:53