Add AbstractController, implement flash message handling, and refactor repositories

This commit is contained in:
2025-03-05 21:01:57 +01:00
parent 29bc74fe4f
commit 0ccce51af8
18 changed files with 111 additions and 21 deletions

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Enum\FlashType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as AbstractBaseController;
abstract class AbstractController extends AbstractBaseController
{
protected function addFlash(FlashType|string $type, mixed $message): void
{
if ($type instanceof FlashType) {
$type = $type->value;
}
parent::addFlash($type, $message);
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\Answer;

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\Candidate;

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\Correction;

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\GivenAnswer;

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\Question;

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\Quiz;

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\Season;

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Entity\User;

View File

@@ -4,15 +4,21 @@ declare(strict_types=1);
namespace App\Controller;
use App\Entity\Answer;
use App\Entity\Candidate;
use App\Entity\GivenAnswer;
use App\Entity\Question;
use App\Entity\Season;
use App\Enum\FlashType;
use App\Form\EnterNameType;
use App\Form\SelectSeasonType;
use App\Helpers\Base64;
use App\Repository\AnswerRepository;
use App\Repository\CandidateRepository;
use App\Repository\GivenAnswerRepository;
use App\Repository\QuestionRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
@@ -42,6 +48,7 @@ class QuizController extends AbstractController
#[Route(path: '/{seasonCode}', name: 'enter_name', requirements: ['seasonCode' => self::SEASON_CODE_REGEX])]
public function enterName(
Request $request,
#[MapEntity(mapping: ['seasonCode' => 'seasonCode'])]
Season $season,
): Response {
$form = $this->createForm(EnterNameType::class);
@@ -64,22 +71,50 @@ class QuizController extends AbstractController
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'nameHash' => self::CANDIDATE_HASH_REGEX],
)]
public function quizPage(
#[MapEntity(mapping: ['seasonCode' => 'seasonCode'])]
Season $season,
string $nameHash,
CandidateRepository $candidateRepository,
QuestionRepository $questionRepository,
AnswerRepository $answerRepository,
GivenAnswerRepository $givenAnswerRepository,
Request $request,
): Response {
$candidate = $candidateRepository->getCandidateByHash($season, $nameHash);
if (!$candidate instanceof Candidate) {
// Add option to add new candidate when preregister is disabled
$this->addFlash(FlashType::Danger->value, 'Candidate not found');
if (false === $season->isPreregisterCandidates()) {
$candidate = new Candidate(Base64::base64_url_decode($nameHash));
$candidateRepository->save($candidate);
} else {
$this->addFlash(FlashType::Danger, 'Candidate not found');
return $this->redirectToRoute('enter_name', ['seasonCode' => $season->getSeasonCode()]);
return $this->redirectToRoute('enter_name', ['seasonCode' => $season->getSeasonCode()]);
}
}
if ('POST' === $request->getMethod()) {
$answer = $answerRepository->findOneBy(['id' => $request->request->get('answer')]);
if (!$answer instanceof Answer) {
throw new BadRequestException('Invalid Answer ID');
}
$givenAnswer = new GivenAnswer();
$givenAnswer->setCandidate($candidate)
->setAnswer($answer)
->setQuiz($answer->getQuestion()->getQuiz());
$givenAnswerRepository->save($givenAnswer);
}
$question = $questionRepository->findNextQuestionForCandidate($candidate);
if (!$question instanceof Question) {
$this->addFlash(FlashType::Success, 'Quiz completed');
return $this->redirectToRoute('enter_name', ['seasonCode' => $season->getSeasonCode()]);
}
return $this->render('quiz/question.twig', ['candidate' => $candidate, 'question' => $question]);
}
}

View File

@@ -12,6 +12,6 @@ enum FlashType: string
case Danger = 'danger';
case Warning = 'warning';
case Info = 'info';
case Ligt = 'light';
case Light = 'light';
case Dark = 'dark';
}

View File

@@ -7,7 +7,6 @@ namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface;
class EnterNameType extends AbstractType
@@ -22,14 +21,6 @@ class EnterNameType extends AbstractType
->add('name', TextType::class,
['required' => true, 'label' => $this->translator->trans('Enter your name')],
)
// ->add('submit', SubmitType::class, ['label' => 'Start quiz'])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
// Configure your form options here
]);
}
}

View File

@@ -14,7 +14,7 @@ class Base64
public static function base64_url_encode(string $input): string
{
return strtr(base64_encode($input), '+/', '-_');
return rtrim(strtr(base64_encode($input), '+/', '-_'), '=');
}
/** @throws UrlException */

View File

@@ -36,4 +36,13 @@ class CandidateRepository extends ServiceEntityRepository
->setParameter('name', $name)
->getQuery()->getOneOrNullResult();
}
public function save(Candidate $candidate, bool $flush = true): void
{
$this->getEntityManager()->persist($candidate);
if (true === $flush) {
$this->getEntityManager()->flush();
}
}
}

View File

@@ -17,4 +17,13 @@ class GivenAnswerRepository extends ServiceEntityRepository
{
parent::__construct($registry, GivenAnswer::class);
}
public function save(GivenAnswer $givenAnswer, bool $flush = true): void
{
$this->getEntityManager()->persist($givenAnswer);
if (true === $flush) {
$this->getEntityManager()->flush();
}
}
}

View File

@@ -20,13 +20,13 @@ class QuestionRepository extends ServiceEntityRepository
parent::__construct($registry, Question::class);
}
public function findNextQuestionForCandidate(Candidate $candidate): Question
public function findNextQuestionForCandidate(Candidate $candidate): ?Question
{
$qb = $this->createQueryBuilder('q');
return $qb->join('q.quiz', 'qz')
->andWhere($qb->expr()->notIn('q.id', $this->getEntityManager()->createQueryBuilder()
->select('ga.id')
->select('q1')
->from(GivenAnswer::class, 'ga')
->join('ga.answer', 'a')
->join('a.question', 'q1')
@@ -38,6 +38,6 @@ class QuestionRepository extends ServiceEntityRepository
->setMaxResults(1)
->setParameter('candidate', $candidate)
->setParameter('quiz', $candidate->getSeason()->getActiveQuiz())
->getQuery()->getSingleResult();
->getQuery()->getOneOrNullResult();
}
}