*/ class ClientTicketRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) { parent::__construct($registry, ClientTicket::class); } /** * Returns the max ticket number for a project, using an advisory lock * to prevent race conditions when creating tickets concurrently. */ public function findMaxNumberByProjectForUpdate(Project $project): int { $conn = $this->getEntityManager()->getConnection(); // Use PostgreSQL advisory lock instead of FOR UPDATE // because FOR UPDATE is not allowed with aggregate functions in PostgreSQL. // Offset by 1000000 to avoid collision with task locks on the same project ID. $conn->executeStatement( 'SELECT pg_advisory_xact_lock(:lockKey)', ['lockKey' => $project->getId() + 1000000], ); $result = $conn->fetchOne( 'SELECT COALESCE(MAX(number), 0) FROM client_ticket WHERE project_id = :project', ['project' => $project->getId()], ); return (int) $result; } }