- Refactor ORM annotations across entities for consistency.

- Adjusted migrations to align with refactored ORM annotations.
- Added new PHPStan and PHP-CS-Fixer configurations, including stricter rules.
- Introduced `tests/object-manager.php` to improve test environment setup.
This commit is contained in:
2025-10-04 12:40:45 +02:00
parent 3e39550c90
commit ca460cca7f
31 changed files with 155 additions and 136 deletions

View File

@@ -5,13 +5,13 @@ use PhpCsFixer\Config;
use PhpCsFixer\Finder; use PhpCsFixer\Finder;
use PhpCsFixer\Runner\Parallel\ParallelConfigFactory; use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;
$finder = (new Finder()) $finder = new Finder()
->in(__DIR__) ->in(__DIR__)
->exclude('var') ->exclude('var')
->exclude('bin') ->exclude('bin')
; ;
return (new Config()) return new Config()
->setParallelConfig(ParallelConfigFactory::detect()) ->setParallelConfig(ParallelConfigFactory::detect())
->setRules([ ->setRules([
'@Symfony' => true, '@Symfony' => true,
@@ -30,8 +30,9 @@ return (new Config())
'single_line_empty_body' => true, 'single_line_empty_body' => true,
'strict_comparison' => true, 'strict_comparison' => true,
'strict_param' => true, 'strict_param' => true,
'ordered_attributes' => true,
'heredoc_indentation' => ['indentation' => 'start_plus_one'],
'trailing_comma_in_multiline' => ['after_heredoc' => true, 'elements' => ['arguments', 'array_destructuring', 'arrays', 'match', 'parameters']], 'trailing_comma_in_multiline' => ['after_heredoc' => true, 'elements' => ['arguments', 'array_destructuring', 'arrays', 'match', 'parameters']],
]) ])
->setRiskyAllowed(true) ->setRiskyAllowed(true)
->setFinder($finder) ->setFinder($finder)

View File

@@ -15,7 +15,6 @@ services:
# See https://xdebug.org/docs/all_settings#mode # See https://xdebug.org/docs/all_settings#mode
XDEBUG_MODE: "${XDEBUG_MODE:-off}" XDEBUG_MODE: "${XDEBUG_MODE:-off}"
MAILER_DSN: "smtp://mailer:1025" MAILER_DSN: "smtp://mailer:1025"
PHP_CS_FIXER_IGNORE_ENV: 1
extra_hosts: extra_hosts:
# Ensure that host.docker.internal is correctly defined on Linux # Ensure that host.docker.internal is correctly defined on Linux
- host.docker.internal:host-gateway - host.docker.internal:host-gateway

View File

@@ -8,3 +8,7 @@ parameters:
- src/ - src/
- tests/ - tests/
treatPhpDocTypesAsCertain: false treatPhpDocTypesAsCertain: false
symfony:
containerXmlPath: var/cache/dev/Tvdt_KernelDevDebugContainer.xml
doctrine:
objectManagerLoader: tests/object-manager.php

View File

@@ -33,12 +33,12 @@ class QuizController extends AbstractController
private readonly TranslatorInterface $translator, private readonly TranslatorInterface $translator,
) {} ) {}
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
#[Route( #[Route(
'/backoffice/season/{seasonCode:season}/quiz/{quiz}', '/backoffice/season/{seasonCode:season}/quiz/{quiz}',
name: 'tvdt_backoffice_quiz', name: 'tvdt_backoffice_quiz',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'quiz' => Requirement::UUID], requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'quiz' => Requirement::UUID],
)] )]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function index(Season $season, Quiz $quiz): Response public function index(Season $season, Quiz $quiz): Response
{ {
return $this->render('backoffice/quiz.html.twig', [ return $this->render('backoffice/quiz.html.twig', [
@@ -48,12 +48,12 @@ class QuizController extends AbstractController
]); ]);
} }
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
#[Route( #[Route(
'/backoffice/season/{seasonCode:season}/quiz/{quiz}/enable', '/backoffice/season/{seasonCode:season}/quiz/{quiz}/enable',
name: 'tvdt_backoffice_enable', name: 'tvdt_backoffice_enable',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'quiz' => Requirement::UUID.'|null'], requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'quiz' => Requirement::UUID.'|null'],
)] )]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function enableQuiz(Season $season, ?Quiz $quiz, EntityManagerInterface $em): RedirectResponse public function enableQuiz(Season $season, ?Quiz $quiz, EntityManagerInterface $em): RedirectResponse
{ {
$season->setActiveQuiz($quiz); $season->setActiveQuiz($quiz);
@@ -66,12 +66,12 @@ class QuizController extends AbstractController
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->getSeasonCode()]); return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->getSeasonCode()]);
} }
#[IsGranted(SeasonVoter::EDIT, subject: 'quiz')]
#[Route( #[Route(
'/backoffice/quiz/{quiz}/clear', '/backoffice/quiz/{quiz}/clear',
name: 'tvdt_backoffice_quiz_clear', name: 'tvdt_backoffice_quiz_clear',
requirements: ['quiz' => Requirement::UUID], requirements: ['quiz' => Requirement::UUID],
)] )]
#[IsGranted(SeasonVoter::EDIT, subject: 'quiz')]
public function clearQuiz(Quiz $quiz, QuizRepository $quizRepository): RedirectResponse public function clearQuiz(Quiz $quiz, QuizRepository $quizRepository): RedirectResponse
{ {
try { try {
@@ -84,12 +84,12 @@ class QuizController extends AbstractController
return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $quiz->getSeason()->getSeasonCode(), 'quiz' => $quiz->getId()]); return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $quiz->getSeason()->getSeasonCode(), 'quiz' => $quiz->getId()]);
} }
#[IsGranted(SeasonVoter::DELETE, subject: 'quiz')]
#[Route( #[Route(
'/backoffice/quiz/{quiz}/delete', '/backoffice/quiz/{quiz}/delete',
name: 'tvdt_backoffice_quiz_delete', name: 'tvdt_backoffice_quiz_delete',
requirements: ['quiz' => Requirement::UUID], requirements: ['quiz' => Requirement::UUID],
)] )]
#[IsGranted(SeasonVoter::DELETE, subject: 'quiz')]
public function deleteQuiz(Quiz $quiz, QuizRepository $quizRepository): RedirectResponse public function deleteQuiz(Quiz $quiz, QuizRepository $quizRepository): RedirectResponse
{ {
$quizRepository->deleteQuiz($quiz); $quizRepository->deleteQuiz($quiz);
@@ -99,12 +99,12 @@ class QuizController extends AbstractController
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $quiz->getSeason()->getSeasonCode()]); return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $quiz->getSeason()->getSeasonCode()]);
} }
#[IsGranted(SeasonVoter::EDIT, subject: 'quiz')]
#[Route( #[Route(
'/backoffice/quiz/{quiz}/candidate/{candidate}/modify_correction', '/backoffice/quiz/{quiz}/candidate/{candidate}/modify_correction',
name: 'tvdt_backoffice_modify_correction', name: 'tvdt_backoffice_modify_correction',
requirements: ['quiz' => Requirement::UUID, 'candidate' => Requirement::UUID], requirements: ['quiz' => Requirement::UUID, 'candidate' => Requirement::UUID],
)] )]
#[IsGranted(SeasonVoter::EDIT, subject: 'quiz')]
public function modifyCorrection(Quiz $quiz, Candidate $candidate, QuizCandidateRepository $quizCandidateRepository, Request $request): RedirectResponse public function modifyCorrection(Quiz $quiz, Candidate $candidate, QuizCandidateRepository $quizCandidateRepository, Request $request): RedirectResponse
{ {
if (!$request->isMethod('POST')) { if (!$request->isMethod('POST')) {

View File

@@ -32,12 +32,12 @@ class SeasonController extends AbstractController
private readonly EntityManagerInterface $em, private readonly EntityManagerInterface $em,
) {} ) {}
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
#[Route( #[Route(
'/backoffice/season/{seasonCode:season}', '/backoffice/season/{seasonCode:season}',
name: 'tvdt_backoffice_season', name: 'tvdt_backoffice_season',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX], requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
)] )]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function index(Season $season, Request $request): Response public function index(Season $season, Request $request): Response
{ {
$form = $this->createForm(SettingsForm::class, $season->getSettings()); $form = $this->createForm(SettingsForm::class, $season->getSettings());
@@ -54,13 +54,13 @@ class SeasonController extends AbstractController
]); ]);
} }
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
#[Route( #[Route(
'/backoffice/season/{seasonCode:season}/add-candidate', '/backoffice/season/{seasonCode:season}/add-candidate',
name: 'tvdt_backoffice_add_candidates', name: 'tvdt_backoffice_add_candidates',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX], requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
priority: 10, priority: 10,
)] )]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function addCandidates(Season $season, Request $request): Response public function addCandidates(Season $season, Request $request): Response
{ {
$form = $this->createForm(AddCandidatesFormType::class); $form = $this->createForm(AddCandidatesFormType::class);
@@ -80,13 +80,13 @@ class SeasonController extends AbstractController
return $this->render('backoffice/season_add_candidates.html.twig', ['form' => $form]); return $this->render('backoffice/season_add_candidates.html.twig', ['form' => $form]);
} }
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
#[Route( #[Route(
'/backoffice/season/{seasonCode:season}/add-quiz', '/backoffice/season/{seasonCode:season}/add-quiz',
name: 'tvdt_backoffice_quiz_add', name: 'tvdt_backoffice_quiz_add',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX], requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
priority: 10, priority: 10,
)] )]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function addQuiz(Request $request, Season $season, QuizSpreadsheetService $quizSpreadsheet): Response public function addQuiz(Request $request, Season $season, QuizSpreadsheetService $quizSpreadsheet): Response
{ {
$quiz = new Quiz(); $quiz = new Quiz();

View File

@@ -28,8 +28,8 @@ final class EliminationController extends AbstractController
{ {
public function __construct(private readonly TranslatorInterface $translator) {} public function __construct(private readonly TranslatorInterface $translator) {}
#[Route('/elimination/{elimination}', name: 'tvdt_elimination', requirements: ['elimination' => Requirement::UUID])]
#[IsGranted(SeasonVoter::ELIMINATION, 'elimination')] #[IsGranted(SeasonVoter::ELIMINATION, 'elimination')]
#[Route('/elimination/{elimination}', name: 'tvdt_elimination', requirements: ['elimination' => Requirement::UUID])]
public function index(#[MapEntity] Elimination $elimination, Request $request): Response public function index(#[MapEntity] Elimination $elimination, Request $request): Response
{ {
$form = $this->createForm(EliminationEnterNameType::class); $form = $this->createForm(EliminationEnterNameType::class);
@@ -48,8 +48,8 @@ final class EliminationController extends AbstractController
]); ]);
} }
#[Route('/elimination/{elimination}/{candidateHash}', name: 'tvdt_elimination_candidate', requirements: ['elimination' => Requirement::UUID, 'candidateHash' => self::CANDIDATE_HASH_REGEX])]
#[IsGranted(SeasonVoter::ELIMINATION, 'elimination')] #[IsGranted(SeasonVoter::ELIMINATION, 'elimination')]
#[Route('/elimination/{elimination}/{candidateHash}', name: 'tvdt_elimination_candidate', requirements: ['elimination' => Requirement::UUID, 'candidateHash' => self::CANDIDATE_HASH_REGEX])]
public function candidateScreen(Elimination $elimination, string $candidateHash, CandidateRepository $candidateRepository): Response public function candidateScreen(Elimination $elimination, string $candidateHash, CandidateRepository $candidateRepository): Response
{ {
$candidate = $candidateRepository->getCandidateByHash($elimination->getQuiz()->getSeason(), $candidateHash); $candidate = $candidateRepository->getCandidateByHash($elimination->getQuiz()->getSeason(), $candidateHash);

View File

@@ -16,17 +16,17 @@ use Tvdt\Repository\AnswerRepository;
#[ORM\Entity(repositoryClass: AnswerRepository::class)] #[ORM\Entity(repositoryClass: AnswerRepository::class)]
class Answer class Answer
{ {
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME)] #[ORM\Column(type: UuidType::NAME)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Id]
private Uuid $id; private Uuid $id;
#[ORM\Column(type: Types::SMALLINT, options: ['default' => 0])] #[ORM\Column(type: Types::SMALLINT, options: ['default' => 0])]
private int $ordering = 0; private int $ordering = 0;
#[ORM\ManyToOne(inversedBy: 'answers')]
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false)]
#[ORM\ManyToOne(inversedBy: 'answers')]
private Question $question; private Question $question;
/** @var Collection<int, Candidate> */ /** @var Collection<int, Candidate> */

View File

@@ -17,14 +17,14 @@ use Tvdt\Repository\CandidateRepository;
#[ORM\UniqueConstraint(fields: ['name', 'season'])] #[ORM\UniqueConstraint(fields: ['name', 'season'])]
class Candidate class Candidate
{ {
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)] #[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Id]
private Uuid $id; private Uuid $id;
#[ORM\ManyToOne(inversedBy: 'candidates')]
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false)]
#[ORM\ManyToOne(inversedBy: 'candidates')]
private Season $season; private Season $season;
/** @var Collection<int, Answer> */ /** @var Collection<int, Answer> */

View File

@@ -21,10 +21,10 @@ class Elimination
public const string SCREEN_RED = 'red'; public const string SCREEN_RED = 'red';
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)] #[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Id]
private Uuid $id; private Uuid $id;
/** @var array<string, mixed> */ /** @var array<string, mixed> */
@@ -35,8 +35,8 @@ class Elimination
private \DateTimeImmutable $created; private \DateTimeImmutable $created;
public function __construct( public function __construct(
#[ORM\ManyToOne(inversedBy: 'eliminations')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
#[ORM\ManyToOne(inversedBy: 'eliminations')]
private Quiz $quiz, private Quiz $quiz,
) {} ) {}

View File

@@ -16,26 +16,26 @@ use Tvdt\Repository\GivenAnswerRepository;
#[ORM\HasLifecycleCallbacks] #[ORM\HasLifecycleCallbacks]
class GivenAnswer class GivenAnswer
{ {
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)] #[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Id]
private Uuid $id; private Uuid $id;
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE, nullable: false)] #[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE, nullable: false)]
private \DateTimeImmutable $created; private \DateTimeImmutable $created;
public function __construct( public function __construct(
#[ORM\ManyToOne(inversedBy: 'givenAnswers')]
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false)]
#[ORM\ManyToOne(inversedBy: 'givenAnswers')]
private Candidate $candidate, private Candidate $candidate,
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
#[ORM\ManyToOne]
private Quiz $quiz, private Quiz $quiz,
#[ORM\ManyToOne(inversedBy: 'givenAnswers')]
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false)]
#[ORM\ManyToOne(inversedBy: 'givenAnswers')]
private Answer $answer, private Answer $answer,
) {} ) {}

View File

@@ -16,10 +16,10 @@ use Tvdt\Repository\QuestionRepository;
#[ORM\Entity(repositoryClass: QuestionRepository::class)] #[ORM\Entity(repositoryClass: QuestionRepository::class)]
class Question class Question
{ {
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME)] #[ORM\Column(type: UuidType::NAME)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Id]
private Uuid $id; private Uuid $id;
#[ORM\Column(type: Types::SMALLINT, options: ['default' => 0])] #[ORM\Column(type: Types::SMALLINT, options: ['default' => 0])]
@@ -28,8 +28,8 @@ class Question
#[ORM\Column(type: Types::STRING, length: 255)] #[ORM\Column(type: Types::STRING, length: 255)]
private string $question; private string $question;
#[ORM\ManyToOne(inversedBy: 'questions')]
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false)]
#[ORM\ManyToOne(inversedBy: 'questions')]
private Quiz $quiz; private Quiz $quiz;
#[ORM\Column] #[ORM\Column]

View File

@@ -16,17 +16,17 @@ use Tvdt\Repository\QuizRepository;
#[ORM\UniqueConstraint(fields: ['name', 'season'])] #[ORM\UniqueConstraint(fields: ['name', 'season'])]
class Quiz class Quiz
{ {
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME)] #[ORM\Column(type: UuidType::NAME)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Id]
private Uuid $id; private Uuid $id;
#[ORM\Column(length: 64)] #[ORM\Column(length: 64)]
private string $name; private string $name;
#[ORM\ManyToOne(inversedBy: 'quizzes')]
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false)]
#[ORM\ManyToOne(inversedBy: 'quizzes')]
private Season $season; private Season $season;
/** @var Collection<int, Question> */ /** @var Collection<int, Question> */

View File

@@ -13,14 +13,14 @@ use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\QuizCandidateRepository; use Tvdt\Repository\QuizCandidateRepository;
#[ORM\Entity(repositoryClass: QuizCandidateRepository::class)] #[ORM\Entity(repositoryClass: QuizCandidateRepository::class)]
#[ORM\UniqueConstraint(columns: ['candidate_id', 'quiz_id'])]
#[ORM\HasLifecycleCallbacks] #[ORM\HasLifecycleCallbacks]
#[ORM\UniqueConstraint(columns: ['candidate_id', 'quiz_id'])]
class QuizCandidate class QuizCandidate
{ {
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)] #[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Id]
private Uuid $id; private Uuid $id;
#[ORM\Column] #[ORM\Column]
@@ -30,12 +30,12 @@ class QuizCandidate
private \DateTimeImmutable $created; private \DateTimeImmutable $created;
public function __construct( public function __construct(
#[ORM\ManyToOne(inversedBy: 'candidateData')]
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false)]
#[ORM\ManyToOne(inversedBy: 'candidateData')]
private Quiz $quiz, private Quiz $quiz,
#[ORM\ManyToOne(inversedBy: 'quizData')]
#[ORM\JoinColumn(nullable: false)] #[ORM\JoinColumn(nullable: false)]
#[ORM\ManyToOne(inversedBy: 'quizData')]
private Candidate $candidate, private Candidate $candidate,
) {} ) {}

View File

@@ -17,10 +17,10 @@ class Season
{ {
private const string SEASON_CODE_CHARACTERS = 'bcdfghjklmnpqrstvwxz'; private const string SEASON_CODE_CHARACTERS = 'bcdfghjklmnpqrstvwxz';
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME)] #[ORM\Column(type: UuidType::NAME)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Id]
private Uuid $id; private Uuid $id;
#[ORM\Column(length: 64)] #[ORM\Column(length: 64)]
@@ -42,12 +42,12 @@ class Season
#[ORM\ManyToMany(targetEntity: User::class, inversedBy: 'seasons')] #[ORM\ManyToMany(targetEntity: User::class, inversedBy: 'seasons')]
private Collection $owners; private Collection $owners;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')] #[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
#[ORM\ManyToOne]
private ?Quiz $ActiveQuiz = null; private ?Quiz $ActiveQuiz = null;
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: true)] #[ORM\JoinColumn(nullable: true)]
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
private ?SeasonSettings $settings = null; private ?SeasonSettings $settings = null;
public function __construct() public function __construct()

View File

@@ -14,10 +14,10 @@ use Tvdt\Repository\SeasonSettingsRepository;
#[ORM\Entity(repositoryClass: SeasonSettingsRepository::class)] #[ORM\Entity(repositoryClass: SeasonSettingsRepository::class)]
class SeasonSettings class SeasonSettings
{ {
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Column(type: UuidType::NAME)] #[ORM\Column(type: UuidType::NAME)]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Id]
private Uuid $id; private Uuid $id;
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])] #[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])]

View File

@@ -22,10 +22,10 @@ use Tvdt\Repository\UserRepository;
#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')] #[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')]
class User implements UserInterface, PasswordAuthenticatedUserInterface class User implements UserInterface, PasswordAuthenticatedUserInterface
{ {
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)] #[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)] #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Id]
private Uuid $id; private Uuid $id;
#[ORM\Column(length: 180)] #[ORM\Column(length: 180)]

View File

@@ -60,10 +60,10 @@ class CandidateRepository extends ServiceEntityRepository
select select
c.id, c.id,
c.name, c.name,
sum(case when a.isRightAnswer = true then 1 else 0 end) as correct, count(case when a.isRightAnswer = true then 1 else null end) as correct,
qc.corrections, qc.corrections,
max(ga.created) - qc.created as time, max(ga.created) - qc.created as time,
(sum(case when a.isRightAnswer = true then 1 else 0 end) + qc.corrections) as score (count(case when a.isRightAnswer = true then 1 else null end) + qc.corrections) as score
from Tvdt\Entity\Candidate c from Tvdt\Entity\Candidate c
join c.givenAnswers ga join c.givenAnswers ga
join ga.answer a join ga.answer a

15
tests/object-manager.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
use Symfony\Component\Dotenv\Dotenv;
use Tvdt\Kernel;
require __DIR__.'/../vendor/autoload.php';
new Dotenv()->bootEnv(__DIR__.'/../.env');
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
return $kernel->getContainer()->get('doctrine')->getManager();