Skip to content

Commit 4db6055

Browse files
caseylockerclaude
andcommitted
refactor(promo-codes): split discover query into targeted per-subtype DQL
getDiscoverableByEmailForSummit loaded ALL 6 discoverable subtypes for the entire summit, then filtered in PHP — O(all codes) hydrations. Split into two focused methods: - getDomainAuthorizedDiscoverableForSummit: fetches only the 2 DA types, filters by email domain in PHP (unavoidable pattern match) - getEmailLinkedDiscoverableForSummit: 4 DQL queries (one per Member/ Speaker × Promo/Discount subtype) push the email filter into the WHERE clause, including the MemberPromoCodeTrait owner-fallback and the PresentationSpeaker member/registration_request two-hop chain Both methods add isLive() date filtering in DQL (:now parameter), matching the codebase convention from DoctrineSummitRepository. The facade method delegates to both and merges, preserving the existing caller contract (discoverPromoCodes and its exhaustion/quota logic are untouched). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 05bcafe commit 4db6055

2 files changed

Lines changed: 121 additions & 34 deletions

File tree

app/Models/Foundation/Summit/Repositories/ISummitRegistrationPromoCodeRepository.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,20 @@ public function getBySummitAndCode(Summit $summit, string $code):?SummitRegistra
8080
*/
8181
public function getDiscoverableByEmailForSummit(Summit $summit, string $email): array;
8282

83+
/**
84+
* @param Summit $summit
85+
* @param string $email
86+
* @return SummitRegistrationPromoCode[]
87+
*/
88+
public function getDomainAuthorizedDiscoverableForSummit(Summit $summit, string $email): array;
89+
90+
/**
91+
* @param Summit $summit
92+
* @param string $email already lowercased and trimmed
93+
* @return SummitRegistrationPromoCode[]
94+
*/
95+
public function getEmailLinkedDiscoverableForSummit(Summit $summit, string $email): array;
96+
8397
/**
8498
* @param Member $member
8599
* @param SummitRegistrationPromoCode $code

app/Repositories/Summit/DoctrineSummitRegistrationPromoCodeRepository.php

Lines changed: 107 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -671,55 +671,128 @@ public function getDiscoverableByEmailForSummit(Summit $summit, string $email):
671671

672672
$email = strtolower(trim($email));
673673

674-
// Fetch all discoverable promo code types for this summit
675-
$qb = $this->getEntityManager()->createQueryBuilder();
676-
$daDiscountClass = DomainAuthorizedSummitRegistrationDiscountCode::class;
677-
$daPromoClass = DomainAuthorizedSummitRegistrationPromoCode::class;
678-
$memberPromoClass = MemberSummitRegistrationPromoCode::class;
679-
$memberDiscountClass = MemberSummitRegistrationDiscountCode::class;
680-
$speakerPromoClass = SpeakerSummitRegistrationPromoCode::class;
681-
$speakerDiscountClass = SpeakerSummitRegistrationDiscountCode::class;
674+
return array_merge(
675+
$this->getDomainAuthorizedDiscoverableForSummit($summit, $email),
676+
$this->getEmailLinkedDiscoverableForSummit($summit, $email)
677+
);
678+
}
679+
680+
/**
681+
* @param Summit $summit
682+
* @param string $email
683+
* @return SummitRegistrationPromoCode[]
684+
*/
685+
public function getDomainAuthorizedDiscoverableForSummit(Summit $summit, string $email): array
686+
{
687+
$em = $this->getEntityManager();
688+
$now = new \DateTime('now', new \DateTimeZone('UTC'));
682689

690+
$qb = $em->createQueryBuilder();
683691
$qb->select('e')
684692
->from($this->getBaseEntity(), 'e')
685-
->leftJoin('e.summit', 's')
693+
->join('e.summit', 's')
686694
->where('s.id = :summit_id')
687-
->andWhere("(e INSTANCE OF {$daDiscountClass} OR e INSTANCE OF {$daPromoClass} OR e INSTANCE OF {$memberPromoClass} OR e INSTANCE OF {$memberDiscountClass} OR e INSTANCE OF {$speakerPromoClass} OR e INSTANCE OF {$speakerDiscountClass})")
688-
->setParameter('summit_id', $summit->getId());
695+
->andWhere(
696+
'(e INSTANCE OF :da_promo OR e INSTANCE OF :da_discount)'
697+
)
698+
->andWhere(
699+
'e.valid_since_date IS NULL OR e.valid_until_date IS NULL '
700+
. 'OR (:now >= e.valid_since_date AND :now <= e.valid_until_date)'
701+
)
702+
->setParameter('summit_id', $summit->getId())
703+
->setParameter('da_promo', DomainAuthorizedSummitRegistrationPromoCode::class)
704+
->setParameter('da_discount', DomainAuthorizedSummitRegistrationDiscountCode::class)
705+
->setParameter('now', $now);
689706

690707
$candidates = $qb->getQuery()->getResult();
691708
$results = [];
692709

693710
foreach ($candidates as $code) {
694-
// Domain-authorized types: match by email domain
695-
if ($code instanceof IDomainAuthorizedPromoCode) {
696-
if ($code->matchesEmailDomain($email) && $code->isLive()) {
697-
$results[] = $code;
698-
}
699-
continue;
700-
}
701-
702-
// Email-linked types: match by associated member/speaker email
703-
if ($code instanceof MemberSummitRegistrationPromoCode || $code instanceof MemberSummitRegistrationDiscountCode) {
704-
$ownerEmail = $code->getOwnerEmail();
705-
if (!empty($ownerEmail) && strtolower($ownerEmail) === $email && $code->isLive()) {
706-
$results[] = $code;
707-
}
708-
continue;
709-
}
710-
711-
if ($code instanceof SpeakerSummitRegistrationPromoCode || $code instanceof SpeakerSummitRegistrationDiscountCode) {
712-
$ownerEmail = $code->getOwnerEmail();
713-
if (!empty($ownerEmail) && strtolower($ownerEmail) === $email && $code->isLive()) {
714-
$results[] = $code;
715-
}
716-
continue;
711+
if ($code instanceof IDomainAuthorizedPromoCode && $code->matchesEmailDomain($email)) {
712+
$results[] = $code;
717713
}
718714
}
719715

720716
return $results;
721717
}
722718

719+
/**
720+
* @param Summit $summit
721+
* @param string $email already lowercased and trimmed
722+
* @return SummitRegistrationPromoCode[]
723+
*/
724+
public function getEmailLinkedDiscoverableForSummit(Summit $summit, string $email): array
725+
{
726+
$em = $this->getEntityManager();
727+
$summitId = $summit->getId();
728+
$now = new \DateTime('now', new \DateTimeZone('UTC'));
729+
730+
$isLiveDql = '(e.valid_since_date IS NULL OR e.valid_until_date IS NULL '
731+
. 'OR (:now >= e.valid_since_date AND :now <= e.valid_until_date))';
732+
733+
// Member promo codes: email on subtype table or fallback to owner Member.Email
734+
$memberPromo = $em->createQueryBuilder()
735+
->select('e')
736+
->from(MemberSummitRegistrationPromoCode::class, 'e')
737+
->join('e.summit', 's')
738+
->leftJoin('e.owner', 'o')
739+
->where('s.id = :summit_id')
740+
->andWhere('LOWER(e.email) = :email OR (e.email IS NULL AND LOWER(o.email) = :email)')
741+
->andWhere($isLiveDql)
742+
->setParameter('summit_id', $summitId)
743+
->setParameter('email', $email)
744+
->setParameter('now', $now)
745+
->getQuery()->getResult();
746+
747+
// Member discount codes
748+
$memberDiscount = $em->createQueryBuilder()
749+
->select('e')
750+
->from(MemberSummitRegistrationDiscountCode::class, 'e')
751+
->join('e.summit', 's')
752+
->leftJoin('e.owner', 'o')
753+
->where('s.id = :summit_id')
754+
->andWhere('LOWER(e.email) = :email OR (e.email IS NULL AND LOWER(o.email) = :email)')
755+
->andWhere($isLiveDql)
756+
->setParameter('summit_id', $summitId)
757+
->setParameter('email', $email)
758+
->setParameter('now', $now)
759+
->getQuery()->getResult();
760+
761+
// Speaker promo codes: email via speaker -> member or speaker -> registration_request
762+
$speakerPromo = $em->createQueryBuilder()
763+
->select('e')
764+
->from(SpeakerSummitRegistrationPromoCode::class, 'e')
765+
->join('e.summit', 's')
766+
->leftJoin('e.speaker', 'sp')
767+
->leftJoin('sp.member', 'm')
768+
->leftJoin('sp.registration_request', 'rr')
769+
->where('s.id = :summit_id')
770+
->andWhere('LOWER(m.email) = :email OR LOWER(rr.email) = :email')
771+
->andWhere($isLiveDql)
772+
->setParameter('summit_id', $summitId)
773+
->setParameter('email', $email)
774+
->setParameter('now', $now)
775+
->getQuery()->getResult();
776+
777+
// Speaker discount codes
778+
$speakerDiscount = $em->createQueryBuilder()
779+
->select('e')
780+
->from(SpeakerSummitRegistrationDiscountCode::class, 'e')
781+
->join('e.summit', 's')
782+
->leftJoin('e.speaker', 'sp')
783+
->leftJoin('sp.member', 'm')
784+
->leftJoin('sp.registration_request', 'rr')
785+
->where('s.id = :summit_id')
786+
->andWhere('LOWER(m.email) = :email OR LOWER(rr.email) = :email')
787+
->andWhere($isLiveDql)
788+
->setParameter('summit_id', $summitId)
789+
->setParameter('email', $email)
790+
->setParameter('now', $now)
791+
->getQuery()->getResult();
792+
793+
return array_merge($memberPromo, $memberDiscount, $speakerPromo, $speakerDiscount);
794+
}
795+
723796
/**
724797
* Count confirmed/paid tickets purchased by a member using a specific promo code.
725798
*

0 commit comments

Comments
 (0)