<?php
namespace App\EventSubscriber\Tournament;
use App\Entity\Contest;
use App\Entity\ContestPlayer;
use App\Entity\Notification;
use App\Entity\Phase;
use App\Entity\Player;
use App\Entity\Result;
use App\Entity\Tournament;
use App\Event\Tournament\Contest\ContestResultCreatedEvent as TournamentContestResultCreatedEvent;
use App\Event\Tournament\PhaseEndedEvent;
use App\Event\Tournament\TournamentEndedEvent;
use App\Helper\PhaseHelper;
use App\Message\CreateNotificationMessage;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Messenger\MessageBusInterface;
class PhaseEndedEventSubscriber implements EventSubscriberInterface
{
public function __construct(
private EntityManagerInterface $entityManager, private EventDispatcherInterface $eventDispatcher,
private MessageBusInterface $bus
) {
}
public function onPhaseEndedEvent(PhaseEndedEvent $phaseEndedEvent): void
{
$endedPhase = $phaseEndedEvent->getPhase();
$tournament = $endedPhase->getTournament();
// loops through all phases to get the awaiting ones
$awaitingPhases = $tournament->getPhases()->filter(function (Phase $phase) {
return $phase->getStatus() === Phase::AWAITING_START_STATUS;
});
// there is no awaiting phase anymore so it means the tournament was already on the last phase and it's now over
if ($awaitingPhases->isEmpty()) {
$tournament
->setStatus(Tournament::ENDED_STATUS)
->setEndedAt(new DateTimeImmutable())
;
$playerIdsCollection = $tournament->getPlayers()->map(function (Player $player) {
return $player->getId();
});
$this->entityManager->flush();
// send notification to all player when match is finished
$this->bus->dispatch(new CreateNotificationMessage(
$playerIdsCollection->toArray(),
Notification::TOURNAMENT_ENDED,
sprintf(
'Tournoi terminé : %s !',
$tournament->getName()
),
['tournamentId' => $tournament->getId()]
));
$this->eventDispatcher->dispatch(new TournamentEndedEvent($tournament));
return;
}
$tournament->setCurrentPhase($awaitingPhases->first());
$players = null;
// gets the winner of each contests of this phase
foreach ($endedPhase->getContests() as $contest) {
if ($contest->getWinner() !== null) {
$players[] = $contest->getWinner();
}
}
// No player has played this phase
if ($players === null) {
$tournament
->setStatus(Tournament::CANCELLED_STATUS)
->setEndedAt(new DateTimeImmutable())
;
$this->entityManager->flush();
return;
}
PhaseHelper::generatePhaseContests($tournament->getCurrentPhase(), $players);
// filters the contests that one have one player
$contests = $tournament->getCurrentPhase()->getContests()->filter(function (Contest $contest) {
return $contest->getContestPlayers()->count() === 1;
});
// loops through all contests that only have one player to make them win automatically
foreach ($contests as $contest) {
/** @var ContestPlayer $contestPlayer */
$contestPlayer = $contest->getContestPlayers()->first();
$player = $contestPlayer->getPlayer();
$player->setXp(
$player->getXp() + $contestPlayer->getXpToEarn()
);
$result = (new Result())
->setScore(0)
->setXp($contestPlayer->getXpToEarn())
->setPlayer($player)
;
$contest->addResult($result);
$this->entityManager->flush();
// dispatches the contest result created event
$this->eventDispatcher->dispatch(new TournamentContestResultCreatedEvent($contest, $result));
}
// starts the phase
$tournament
->getCurrentPhase()
->setStatus(Phase::STARTED_STATUS)
->setStartedAt(new DateTimeImmutable())
;
/** @var Phase[] $upcomingPhases */
$upcomingPhases = $tournament->getPhases()->filter(function (Phase $phase) use ($tournament) {
return $phase->getStatus() === Phase::AWAITING_START_STATUS && $phase !== $tournament->getCurrentPhase();
});
$previousPhase = null;
// loops through all upcoming phases and changes their start/end date
foreach ($upcomingPhases as $upcomingPhase) {
$previousPhase = $previousPhase ?? $tournament->getCurrentPhase();
$startDate = $previousPhase->getEndDate();
$upcomingPhase->setStartDate($startDate);
$previousPhase = $upcomingPhase;
}
foreach ($players as $player) {
$player->addNotification(
(new Notification())
->setType(Notification::TOURNAMENT_PHASE_STARTED)
->setMessage(sprintf(
'%s : Une nouvelle phase commence !',
$tournament->getName()
))
->setExtraData(['tournamentId' => $tournament->getId()])
);
}
$this->entityManager->flush();
}
public static function getSubscribedEvents(): array
{
return [
PhaseEndedEvent::class => 'onPhaseEndedEvent',
];
}
}