<?php
namespace App\Entity;
use App\Entity\Traits\Timestampable;
use App\Repository\QuestionRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
// Question d'une Activity. `type` reprend en général celui de l'Activity
// parente mais reste porté ici pour permettre, à terme, des activités mixtes.
// `matchMode` ne s'applique qu'aux questions de type short_answer (stratégie
// de correction du texte libre : exact|ci [insensible à la casse]|contains).
#[ORM\HasLifecycleCallbacks]
#[ORM\Entity(repositoryClass: QuestionRepository::class)]
class Question
{
use Timestampable;
public const MATCH_EXACT = 'exact';
public const MATCH_CASE_INSENSITIVE = 'ci';
public const MATCH_CONTAINS = 'contains';
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(inversedBy: 'questions')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private ?Activity $activity = null;
#[ORM\Column(length: 20)]
private ?string $type = null;
#[ORM\Column(type: 'text')]
private ?string $text = null;
#[ORM\Column]
private int $position = 0;
#[ORM\Column(length: 20, nullable: true)]
private ?string $matchMode = null;
// Compétence évaluée par CETTE question précise (module Apprentissage) —
// niveau le plus spécifique de la chaîne de résolution, voir
// getEffectiveLearningCompetency(). Utile quand un même exercice mélange
// des questions qui ne testent pas toutes le même savoir-faire.
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
private ?LearningCompetency $learningCompetency = null;
#[ORM\OneToMany(mappedBy: 'question', targetEntity: QuestionOption::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
#[ORM\OrderBy(['position' => 'ASC'])]
private Collection $options;
public function __construct()
{
$this->options = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getActivity(): ?Activity
{
return $this->activity;
}
public function setActivity(?Activity $activity): static
{
$this->activity = $activity;
return $this;
}
public function getType(): ?string
{
return $this->type;
}
public function setType(string $type): static
{
$this->type = $type;
return $this;
}
public function getText(): ?string
{
return $this->text;
}
public function setText(string $text): static
{
$this->text = $text;
return $this;
}
public function getPosition(): int
{
return $this->position;
}
public function setPosition(int $position): static
{
$this->position = $position;
return $this;
}
public function getMatchMode(): ?string
{
return $this->matchMode;
}
public function setMatchMode(?string $matchMode): static
{
$this->matchMode = $matchMode;
return $this;
}
public function getLearningCompetency(): ?LearningCompetency
{
return $this->learningCompetency;
}
public function setLearningCompetency(?LearningCompetency $learningCompetency): static
{
$this->learningCompetency = $learningCompetency;
return $this;
}
/**
* Compétence réellement mesurée par cette question, en remontant la
* chaîne question → activité → capsule jusqu'à trouver un niveau qui en
* précise une (le plus spécifique gagne) — voir demande utilisateur :
* "associer une capsule ou un quiz à cette compétence, ou des questions
* des exercices à cette compétence".
*/
public function getEffectiveLearningCompetency(): ?LearningCompetency
{
return $this->learningCompetency
?? $this->activity?->getLearningCompetency()
?? $this->activity?->getBlock()?->getCapsule()?->getLearningCompetency();
}
/** @return Collection<int, QuestionOption> */
public function getOptions(): Collection
{
return $this->options;
}
public function addOption(QuestionOption $option): static
{
if (!$this->options->contains($option)) {
$this->options->add($option);
$option->setQuestion($this);
}
return $this;
}
public function removeOption(QuestionOption $option): static
{
if ($this->options->removeElement($option)) {
if ($option->getQuestion() === $this) {
$option->setQuestion(null);
}
}
return $this;
}
}