How to avoid multiple uses of the same name? - php

I'm trying to make this little project using the symfony framework, it's pretty simple, but I'm still new to it.
I have 3 entities, Classe, Student and WorkDays. class has a OneToMany relation to Student, and another OneToMany relation to schedule.
When creating a class, you can add students and WorkDays
here's my code:
Classe entity:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ClasseRepository")
*/
class Classe
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=200)
*/
private $label;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Student", mappedBy="classe", orphanRemoval=true)
*/
private $Students;
/**
* #ORM\OneToMany(targetEntity="App\Entity\WorkDays", mappedBy="class")
*/
private $schedule;
public function __construct()
{
$this->Students = new ArrayCollection();
$this->schedule = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getLabel(): ?string
{
return $this->label;
}
public function setLabel(string $label): self
{
$this->label = $label;
return $this;
}
/**
* #return Collection|Student[]
*/
public function getStudents(): Collection
{
return $this->Students;
}
public function addStudent(Student $student): self
{
if (!$this->Students->contains($student)) {
$this->Students[] = $student;
$student->setClasse($this);
}
return $this;
}
public function removeStudent(Student $student): self
{
if ($this->Students->contains($student)) {
$this->Students->removeElement($student);
// set the owning side to null (unless already changed)
if ($student->getClasse() === $this) {
$student->setClasse(null);
}
}
return $this;
}
/**
* #return Collection|WorkDays[]
*/
public function getSchedule(): Collection
{
return $this->schedule;
}
public function addSchedule(WorkDays $schedule): self
{
if (!$this->schedule->contains($schedule)) {
$this->schedule[] = $schedule;
$schedule->setClass($this);
}
return $this;
}
public function removeSchedule(WorkDays $schedule): self
{
if ($this->schedule->contains($schedule)) {
$this->schedule->removeElement($schedule);
// set the owning side to null (unless already changed)
if ($schedule->getClass() === $this) {
$schedule->setClass(null);
}
}
return $this;
}
}
student entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\StudentRepository")
*/
class Student
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=200)
*/
private $firstname;
/**
* #ORM\Column(type="string", length=200)
*/
private $lastname;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\classe", inversedBy="Students")
* #ORM\JoinColumn(nullable=false)
*/
private $classe;
public function getId(): ?int
{
return $this->id;
}
public function getFirstname(): ?string
{
return $this->firstname;
}
public function setFirstname(string $firstname): self
{
$this->firstname = $firstname;
return $this;
}
public function getLastname(): ?string
{
return $this->lastname;
}
public function setLastname(string $lastname): self
{
$this->lastname = $lastname;
return $this;
}
public function getClasse(): ?classe
{
return $this->classe;
}
public function setClasse(?classe $classe): self
{
$this->classe = $classe;
return $this;
}
}
and WorkDays
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\WorkDaysRepository")
*/
class WorkDays
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=200)
*/
private $day;
/**
* #ORM\Column(type="time", nullable=true)
*/
private $start;
/**
* #ORM\Column(type="time")
*/
private $end;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Classe", inversedBy="schedule")
*/
private $class;
public function getId(): ?int
{
return $this->id;
}
public function getDay(): ?string
{
return $this->day;
}
public function setDay(string $day): self
{
$this->day = $day;
return $this;
}
public function getStart(): ?\DateTimeInterface
{
return $this->start;
}
public function setStart(?\DateTimeInterface $start): self
{
$this->start = $start;
return $this;
}
public function getEnd(): ?\DateTimeInterface
{
return $this->end;
}
public function setEnd(\DateTimeInterface $end): self
{
$this->end = $end;
return $this;
}
public function getClass(): ?Classe
{
return $this->class;
}
public function setClass(?Classe $class): self
{
$this->class = $class;
return $this;
}
}
finally this is my form, I used CollectionType to include Students and workdays and I used this plugin: https://github.com/ninsuo/symfony-collection to add as many students and workdays as I want
<?php
class ClasseType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('Label')
->add('Students', CollectionType::class, [
'label' => 'Students',
'entry_type' => StudentsType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'by_reference' => false,
'delete_empty' => true,
'attr' => [
'class' => 'collection',
],
])
->add('schedule', CollectionType::class, [
'label' => 'schedule',
'entry_type' => scheduleType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'by_reference' => false,
'delete_empty' => true,
'attr' => [
'class' => 'collection',
],
])
;
$builder->add('save', SubmitType::class, [
'label' => 'See my addresses',
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Classe::class,
]);
}
}
now, everything works fine, adding student in this form would also add them in their respectable table. Same thing with work days, which let's you choose the day from a Entitytype drop-down
My problem:
Is there any way I can avoid duplicate students names and workdays names ? same with class names ?

Set unique attribute of the properties which have to be unique just as mentioned in the comments. And you can catch any QueryException of Doctrine’s and send back appropriate error message/response to the client.

Related

Can't get a way to read the property "member" in class "App\Entity\Invoice"

I have an error when I use CollectionType:
https://symfony.com/doc/current/form/form_collections.html
I followed the documentation but I got the following error:
Can't get a way to read the property "member" in class "App\Entity\Invoice".
My controller:
#[Route('/subscription/{id}/new-invoice', name: 'new_invoice')]
public function newInvoice(Event $event,Request $request):Response
{
$options['responsibleAdult'] = $this->getUser()->getId();
$options['event'] = $event->getId();
$listEventOption = $this->entityManager->getRepository(EventOption::class)->findBy(['event' => $event->getId()]);
$resultListEventOption = [];
$i=1;
foreach($listEventOption as $value){
$resultListEventOption[$value->getName()] = $i;
$i++;
}
$options['eventOptions'] = $resultListEventOption;
$invoice = new Invoice();
$form = $this->createForm(InvoiceType::class, $invoice, $options);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$invoice = $form->getData();
//...
}
return $this->render('subscription/new-invoice.html.twig', [
'event' => $event,
'subscriptionId' => $event->getId(),
'form' => $form->createView(),
]);
}
My InvoiceType:
class InvoiceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('eventSubscriptions', CollectionType::class, [
'entry_type' => MemberEventSubscriptionType::class,
'label' =>false,
'entry_options' => $options,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Invoice::class,
'responsibleAdult' => "",
'event' =>"",
'eventOptions' => []
]);
}
}
My MemberEventSubscriptionType:
class MemberEventSubscriptionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('member', EntityType::class, [
'class' => Member::class,
'label' => false,
'query_builder' => function (MemberRepository $mr) use($options) {
return $mr->findForEventSubscription($options);
},
])
->add('eventRate', EntityType::class, [
'class' => EventRate::class,
'label' => false,
'query_builder' => function (EventRateRepository $evr) {
return $evr->createQueryBuilder('er')
->orderBy('er.amount', 'ASC');
},
])
->add('eventOption', ChoiceType::class, [
'label' => false,
'expanded' => true,
'multiple' => true,
'choices' => $options['eventOptions']
])
->add('submit', SubmitType::class, [
'label' => 'Valider l\'inscription',
'attr' => [
'class' => 'btn-block btn-dark'
]
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => EventSubscription::class,
'csrf_protection' => false,
'responsibleAdult' => "",
'event' =>"",
'eventOptions' => []
]);
}
My Invoice:
<?php
namespace App\Entity;
use App\Repository\InvoiceRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=InvoiceRepository::class)
*/
class Invoice
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var Collection|EventSubscription[]
* #ORM\OneToMany(targetEntity=EventSubscription::class, mappedBy="invoice")
* #ORM\JoinTable(name="event_subscription_invoice")
*/
protected Collection $eventSubscriptions;
/**
* #ORM\Column(type="boolean")
*/
private $isPaid;
public function __construct()
{
$this->eventSubscriptions = new ArrayCollection();
$this->tugs = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
/** #return EventSubscription[] */
public function getEventSubscriptions(): Collection
{
return $this->eventSubscriptions;
}
public function isIsPaid(): ?bool
{
return $this->isPaid;
}
public function setIsPaid(bool $isPaid): self
{
$this->isPaid = $isPaid;
return $this;
}
public function addEventSubscription(EventSubscription $eventSubscription): void
{
$this->eventSubscriptions->add($eventSubscription);
}
public function removeEventSubscription(EventSubscription $eventSubscription): self
{
if ($this->invoice->contains($eventSubscription)) {
$this->invoice->removeElement($eventSubscription);
}
return $this;
}
}
My EventSubscription:
<?php
namespace App\Entity;
use App\Repository\EventSubscriptionRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass=EventSubscriptionRepository::class)
*/
class EventSubscription
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=Event::class, inversedBy="eventSubscriptions")
* #ORM\JoinColumn(nullable=false)
*/
private Event $event;
/**
* #ORM\ManyToOne(targetEntity=Member::class, inversedBy="eventSubscriptions")
* #ORM\JoinColumn(nullable=false)
*/
private Member $member;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="eventSubscriptions")
* #ORM\JoinColumn(nullable=false)
*/
private User $user;
/**
* #ORM\ManyToOne(targetEntity=EventRate::class, inversedBy="eventSubscriptions")
* #ORM\JoinColumn(nullable=false)
*/
private EventRate $eventRate;
/**
* #ORM\ManyToMany(targetEntity=EventOption::class, inversedBy="eventSubscriptions")
* #ORM\JoinTable(name="event_subscription_event_option")
*/
private Collection $eventOptions;
/**
* #Assert\Choice({"en attente de pièces", "résiliée", "ok"})
* #ORM\Column(type="string", length=255)
*/
private ?string $status;
/**
* #ORM\ManyToOne(targetEntity=Payment::class, inversedBy="eventSubscriptions", cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
*/
private Payment $payment;
/**
* #ORM\Column(type="string", length=255, nullable=true)
* #Assert\File(mimeTypes={"image/gif", "image/jpeg", "image/png", "image/pdf"})
*/
private ?string $medicalCertificateName;
/**
* #ORM\Column(type="string", length=5000, nullable=true)
*/
private ?string $comment;
/**
* #ORM\Column(type="boolean")
*/
private $isPaid = false;
/**
* #ORM\ManyToOne(targetEntity=Invoice::class, inversedBy="eventSubscriptions", cascade={"persist"})
* #ORM\JoinColumn(nullable=false)
*/
private Invoice $invoice;
public function __construct()
{
$this->eventOptions = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getEvent(): Event
{
return $this->event;
}
public function setEvent(Event $event): self
{
$this->event = $event;
return $this;
}
public function getMember(): Member
{
return $this->member;
}
public function setMember(Member $member): self
{
$this->member = $member;
return $this;
}
public function getUser(): User
{
return $this->user;
}
public function setUser(User $user): self
{
$this->user = $user;
return $this;
}
public function getEventRate(): EventRate
{
return $this->eventRate;
}
public function setEventRate(EventRate $eventRate): self
{
$this->eventRate = $eventRate;
return $this;
}
/**
* #return Collection<int, EventOption>
*/
public function getEventOptions(): Collection
{
return $this->eventOptions;
}
public function addEventOption(EventOption $eventOption): self
{
if (!$this->eventOptions->contains($eventOption)) {
$this->eventOptions[] = $eventOption;
}
return $this;
}
public function removeEventOption(EventOption $eventOption): self
{
$this->eventOptions->removeElement($eventOption);
return $this;
}
public function getStatus(): ?string
{
return $this->status;
}
public function setStatus(string $status): self
{
$this->status = $status;
return $this;
}
public function getPayment(): Payment
{
return $this->payment;
}
public function setPayment(Payment $payment): self
{
$this->payment = $payment;
return $this;
}
public function getMedicalCertificateName(): ?string
{
return $this->medicalCertificateName;
}
public function setMedicalCertificateName(string $medicalCertificateName): self
{
$this->medicalCertificateName = $medicalCertificateName;
return $this;
}
/**
* #return string|null
*/
public function getComment(): ?string
{
return $this->comment;
}
/**
* #param string|null $comment
*/
public function setComment(?string $comment): void
{
$this->comment = $comment;
}
public function isIsPaid(): ?bool
{
return $this->isPaid;
}
public function setIsPaid(bool $isPaid): self
{
$this->isPaid = $isPaid;
return $this;
}
public function getInvoice(): Invoice
{
return $this->invoice;
}
public function setInvoice(Invoice $invoice): self
{
$this->invoice = $invoice;
return $this;
}
}
HTML:
<button type="button" class="add_item_link btn btn-dark btn-sm" data-collection-holder-class="eventSubscriptions">Ajouter un membre</button>
{{ form_start(form) }}
<ul class="eventSubscriptions mt-2"
data-index="{{ form.eventSubscriptions|length > 0 ? form.eventSubscriptions|last.vars.name + 1 : 0 }}"
data-prototype="{{ form_widget(form.eventSubscriptions.vars.prototype)|e('html_attr') }}"
></ul>
{{ form_end(form) }}
JS:
const addFormToCollection = (e) => {
const collectionHolder = document.querySelector('.' + e.currentTarget.dataset.collectionHolderClass);
const item = document.createElement('div');
item.innerHTML = collectionHolder
.dataset
.prototype
.replace(
/__name__/g,
collectionHolder.dataset.index
);
collectionHolder.appendChild(item);
collectionHolder.dataset.index++;
};
Have you a solution?
The error appears on:
$form = $this->createForm(InvoiceType::class, $invoice, $options);
PS:
Mapped false doesn't work, because it create other problems.
I have tested :
$form = $this->createForm(InvoiceType::class, null, $options);
but when I submit I have the same error.
So I found the solution:
I have change my entry_options in my InvoiceType:
'entry_options' => [
"responsibleAdult" => $options['responsibleAdult'],
"event" => $options['event'],
"eventOptions" => $options['eventOptions'],
],

Symfony 4 quiz style form, embedded collection types

I'm trying to display a quiz style form where end-user gets presented with questions that can have one or multiple correct answers.
Entities:
Exam Question
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Timestampable\Traits\TimestampableEntity;
use Ramsey\Uuid\UuidInterface;
/**
* #ORM\Entity(repositoryClass="App\Repository\ExamQuestionRepository")
*/
class ExamQuestion
{
use TimestampableEntity;
/**
* #var UuidInterface
*
* #ORM\Id
* #ORM\Column(type="uuid", unique=true)
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidGenerator")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Module", inversedBy="questions")
* #ORM\JoinColumn(nullable=false)
*/
private $module;
/**
* #ORM\Column(type="text")
*/
private $text;
/**
* #ORM\OneToMany(targetEntity="App\Entity\ExamQuestionAnswer", mappedBy="question", orphanRemoval=true, cascade={"persist"})
*/
private $examQuestionAnswers;
/**
* #ORM\Column(type="boolean")
*/
private $hasMultipleAnswers;
public function __construct()
{
$this->examQuestionAnswers = new ArrayCollection();
}
public function __toString()
{
return $this->text;
}
public function getId(): ?UuidInterface
{
return $this->id;
}
public function getModule(): ?Module
{
return $this->module;
}
public function setModule(?Module $module): self
{
$this->module = $module;
return $this;
}
public function getText(): ?string
{
return $this->text;
}
public function setText(string $text): self
{
$this->text = $text;
return $this;
}
/**
* #return Collection|ExamQuestionAnswer[]
*/
public function getExamQuestionAnswers(): Collection
{
return $this->examQuestionAnswers;
}
public function addExamQuestionAnswer(ExamQuestionAnswer $examQuestionAnswer): self
{
if (!$this->examQuestionAnswers->contains($examQuestionAnswer)) {
$this->examQuestionAnswers[] = $examQuestionAnswer;
$examQuestionAnswer->setQuestion($this);
}
return $this;
}
public function removeExamQuestionAnswer(ExamQuestionAnswer $examQuestionAnswer): self
{
if ($this->examQuestionAnswers->contains($examQuestionAnswer)) {
$this->examQuestionAnswers->removeElement($examQuestionAnswer);
// set the owning side to null (unless already changed)
if ($examQuestionAnswer->getQuestion() === $this) {
$examQuestionAnswer->setQuestion(null);
}
}
return $this;
}
public function getHasMultipleAnswers(): ?bool
{
return $this->hasMultipleAnswers;
}
public function setHasMultipleAnswers(bool $hasMultipleAnswers): self
{
$this->hasMultipleAnswers = $hasMultipleAnswers;
return $this;
}
}
Exam Question Answer
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Timestampable\Traits\TimestampableEntity;
use Ramsey\Uuid\UuidInterface;
/**
* #ORM\Entity(repositoryClass="App\Repository\ExamQuestionRepository")
*/
class ExamQuestionAnswer
{
use TimestampableEntity;
/**
* #var UuidInterface
*
* #ORM\Id
* #ORM\Column(type="uuid", unique=true)
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidGenerator")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\ExamQuestion", inversedBy="examQuestionAnswers")
* #ORM\JoinColumn(nullable=false)
*/
private $question;
/**
* #ORM\Column(type="boolean")
*/
private $isCorrect;
/**
* #ORM\Column(type="text")
*/
private $text;
/**
* #ORM\Column(type="boolean", nullable=true)
*/
private $selected;
public function __toString()
{
return $this->text;
}
public function getId(): ?UuidInterface
{
return $this->id;
}
public function getQuestion(): ExamQuestion
{
return $this->question;
}
public function setQuestion(ExamQuestion $question): self
{
$this->question = $question;
return $this;
}
public function getIsCorrect(): ?bool
{
return $this->isCorrect;
}
public function setIsCorrect(bool $isCorrect): self
{
$this->isCorrect = $isCorrect;
return $this;
}
public function getText(): ?string
{
return $this->text;
}
public function setText(string $text): self
{
$this->text = $text;
return $this;
}
public function getSelected(): ?bool
{
return $this->selected;
}
public function setSelected(?bool $selected): self
{
$this->selected = $selected;
return $this;
}
}
Exam Take
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Timestampable\Traits\TimestampableEntity;
use Ramsey\Uuid\UuidInterface;
/**
* #ORM\Entity(repositoryClass="App\Repository\ExamTakeRepository")
*/
class ExamTake
{
use TimestampableEntity;
/**
* #var UuidInterface
*
* #ORM\Id
* #ORM\Column(type="uuid", unique=true)
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidGenerator")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\ExamQuestion")
*/
private $questions;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Module", inversedBy="examTakes")
* #ORM\JoinColumn(nullable=false)
*/
private $module;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Student", inversedBy="examTakes")
* #ORM\JoinColumn(nullable=false)
*/
private $student;
/**
* #ORM\Column(type="boolean", nullable=true)
*/
private $passed;
/**
* #ORM\OneToMany(targetEntity="App\Entity\ExamQuestionStudentAnswer", mappedBy="examTake", orphanRemoval=true)
*/
private $examQuestionStudentAnswers;
public function __construct()
{
$this->questions = new ArrayCollection();
$this->examQuestionStudentAnswers = new ArrayCollection();
}
public function getId(): ?UuidInterface
{
return $this->id;
}
/**
* #return Collection|ExamQuestion[]
*/
public function getQuestions(): Collection
{
return $this->questions;
}
public function addQuestion(ExamQuestion $question): self
{
if (!$this->questions->contains($question)) {
$this->questions[] = $question;
}
return $this;
}
public function removeQuestion(ExamQuestion $question): self
{
if ($this->questions->contains($question)) {
$this->questions->removeElement($question);
}
return $this;
}
public function getModule(): ?Module
{
return $this->module;
}
public function setModule(?Module $module): self
{
$this->module = $module;
return $this;
}
public function getStudent(): ?Student
{
return $this->student;
}
public function setStudent(?Student $student): self
{
$this->student = $student;
return $this;
}
public function getPassed(): ?bool
{
return $this->passed;
}
public function setPassed(?bool $passed): self
{
$this->passed = $passed;
return $this;
}
/**
* #return Collection|ExamQuestionStudentAnswer[]
*/
public function getExamQuestionStudentAnswers(): Collection
{
return $this->examQuestionStudentAnswers;
}
public function addExamQuestionStudentAnswer(ExamQuestionStudentAnswer $examQuestionStudentAnswer): self
{
if (!$this->examQuestionStudentAnswers->contains($examQuestionStudentAnswer)) {
$this->examQuestionStudentAnswers[] = $examQuestionStudentAnswer;
$examQuestionStudentAnswer->setExamTake($this);
}
return $this;
}
public function removeExamQuestionStudentAnswer(ExamQuestionStudentAnswer $examQuestionStudentAnswer): self
{
if ($this->examQuestionStudentAnswers->contains($examQuestionStudentAnswer)) {
$this->examQuestionStudentAnswers->removeElement($examQuestionStudentAnswer);
// set the owning side to null (unless already changed)
if ($examQuestionStudentAnswer->getExamTake() === $this) {
$examQuestionStudentAnswer->setExamTake(null);
}
}
return $this;
}
}
And the forms:
class ExamTakeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('questions', CollectionType::class, [
'entry_type' => ExamQuestionType::class,
'allow_add' => false,
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ExamTake::class,
]);
}
}
class ExamQuestionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('text', TextType::class, [
'attr' => ['readonly' => true],
])
->add('examQuestionAnswers', CollectionType::class, [
'entry_type' => ExamQuestionAnswerType::class,
'allow_add' => false,
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ExamQuestion::class,
]);
}
}
class ExamQuestionAnswerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('text');
$builder->addEventListener(FormEvents::POST_SET_DATA, static function (FormEvent $event) {
$form = $event->getForm();
/** #var ExamQuestion $question */
$question = $event->getData()->getQuestion();
if ($question->getHasMultipleAnswers()) {
$form
->add('select', ChoiceType::class, [
'expanded' => true,
'multiple' => true,
'mapped' => false,
]);
} else {
$form
->add('select', ChoiceType::class, [
'expanded' => true,
'multiple' => false,
'mapped' => false,
]);
}
});
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ExamQuestionAnswer::class,
]);
}
}
It seems i can't get the examQuestionAnswers field in collection displayed correctly. What i get is a collection of unrelated fields(inputs) if question has only one answer, and if question has multiple answers checkboxes are not shown. Any help is much appreciated!
The ChoiceType expects the choices attribute"
if ($question->getHasMultipleAnswers()) {
$form
->add('select', ChoiceType::class, [
'choices' => $question->getExamQuestionAnswers(),
'expanded' => true,
'multiple' => true,
'mapped' => false,
]);
} else {
$form
->add('select', ChoiceType::class, [
'choices' => $question->getExamQuestionAnswers(),
'expanded' => true,
'multiple' => false,
'mapped' => false,
]);
}
This will result in putting $examQuestionAnswers as choices to your ChoiceType. In case you get an exception change the type to EntityType(it inherits from ChoiceType) so choices should still work the same. You might also needed to implement either a __toString() method in your ExamQuestionAnswer entity or define choice_label attribute this should solve your problem in case it doesn't add a comment about what does not work and I'll implement it myself

Form issue when trying to create a checkbox with CollectionType calling to another FromType (cross entities situation)

This case is a case study, I'm trying to resolve this issue in order to explain how to organize entities and create forms to my students.
I have this singular relation between 3 of my entities :
Protagonist <--(OneToMany)--> EventRegistration <--(ManyToOne)--> Event
Which could not be transformed as a many to many relation because there are some columns inside the EventRegistration table :
Protagonist :
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProtagonistRepository")
*/
class Protagonist
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #ORM\Column(type="string", length=100, nullable=true)
*/
private $japaneseName;
/**
* #ORM\Column(type="text")
*/
private $description;
/**
* #ORM\Column(type="string", length=80, nullable=true)
*/
private $picture;
/**
* #ORM\Column(type="string", length=80, nullable=true)
*/
private $background;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
private $updated_at;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="protagonists")
* #ORM\JoinColumn(nullable=false)
*/
private $category;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Tag", mappedBy="protagonists")
*/
private $tags;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Registration", mappedBy="protagonist")
*/
private $registrations;
/**
* #ORM\Column(type="boolean", nullable=true)
*/
private $isAlive;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Event", mappedBy="protagonists")
*/
private $events;
/**
* #ORM\OneToMany(targetEntity="App\Entity\EventRegistration", mappedBy="protagonist")
*/
private $eventRegistrations;
public function __construct()
{
$this->tags = new ArrayCollection();
$this->registrations = new ArrayCollection();
$this->events = new ArrayCollection();
$this->eventRegistrations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getJapaneseName(): ?string
{
return $this->japaneseName;
}
public function setJapaneseName(?string $japaneseName): self
{
$this->japaneseName = $japaneseName;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(string $description): self
{
$this->description = $description;
return $this;
}
public function getPicture(): ?string
{
return $this->picture;
}
public function setPicture(?string $picture): self
{
$this->picture = $picture;
return $this;
}
public function getBackground(): ?string
{
return $this->background;
}
public function setBackground(?string $background): self
{
$this->background = $background;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updated_at;
}
public function setUpdatedAt(?\DateTimeInterface $updated_at): self
{
$this->updated_at = $updated_at;
return $this;
}
public function getCategory(): ?Category
{
return $this->category;
}
public function setCategory(?Category $category): self
{
$this->category = $category;
return $this;
}
/**
* #return Collection|Tag[]
*/
public function getTags(): Collection
{
return $this->tags;
}
public function addTag(Tag $tag): self
{
if (!$this->tags->contains($tag)) {
$this->tags[] = $tag;
$tag->addProtagonist($this);
}
return $this;
}
public function removeTag(Tag $tag): self
{
if ($this->tags->contains($tag)) {
$this->tags->removeElement($tag);
$tag->removeProtagonist($this);
}
return $this;
}
/**
* #return Collection|Registration[]
*/
public function getRegistrations(): Collection
{
return $this->registrations;
}
public function addRegistration(Registration $registration): self
{
if (!$this->registrations->contains($registration)) {
$this->registrations[] = $registration;
$registration->setProtagonist($this);
}
return $this;
}
public function removeRegistration(Registration $registration): self
{
if ($this->registrations->contains($registration)) {
$this->registrations->removeElement($registration);
// set the owning side to null (unless already changed)
if ($registration->getProtagonist() === $this) {
$registration->setProtagonist(null);
}
}
return $this;
}
public function getIsAlive(): ?bool
{
return $this->isAlive;
}
public function setIsAlive(?bool $isAlive): self
{
$this->isAlive = $isAlive;
return $this;
}
/**
* #return Collection|Event[]
*/
public function getEvents(): Collection
{
return $this->events;
}
public function addEvent(Event $event): self
{
if (!$this->events->contains($event)) {
$this->events[] = $event;
$event->addProtagonist($this);
}
return $this;
}
public function removeEvent(Event $event): self
{
if ($this->events->contains($event)) {
$this->events->removeElement($event);
$event->removeProtagonist($this);
}
return $this;
}
/**
* #return Collection|EventRegistration[]
*/
public function getEventRegistrations(): Collection
{
return $this->eventRegistrations;
}
public function addEventRegistration(EventRegistration $eventRegistration): self
{
if (!$this->eventRegistrations->contains($eventRegistration)) {
$this->eventRegistrations[] = $eventRegistration;
$eventRegistration->setProtagonist($this);
}
return $this;
}
public function removeEventRegistration(EventRegistration $eventRegistration): self
{
if ($this->eventRegistrations->contains($eventRegistration)) {
$this->eventRegistrations->removeElement($eventRegistration);
// set the owning side to null (unless already changed)
if ($eventRegistration->getProtagonist() === $this) {
$eventRegistration->setProtagonist(null);
}
}
return $this;
}
}
EventRegistration :
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\EventRegistrationRepository")
*/
class EventRegistration
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="datetimetz")
*/
private $registrationDate;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Protagonist", inversedBy="eventRegistrations")
*/
private $protagonist;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Event", inversedBy="eventRegistrations")
*/
private $event;
public function getId(): ?int
{
return $this->id;
}
public function getRegistrationDate(): ?\DateTimeInterface
{
return $this->registrationDate;
}
public function setRegistrationDate(\DateTimeInterface $registrationDate): self
{
$this->registrationDate = $registrationDate;
return $this;
}
public function getProtagonist(): ?Protagonist
{
return $this->protagonist;
}
public function setProtagonist(?Protagonist $protagonist): self
{
$this->protagonist = $protagonist;
return $this;
}
public function getEvent(): ?Event
{
return $this->event;
}
public function setEvent(?Event $event): self
{
$this->event = $event;
return $this;
}
}
Event :
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\EventRepository")
*/
class Event
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #ORM\Column(type="datetimetz", nullable=true)
*/
private $start_date;
/**
* #ORM\Column(type="datetimetz", nullable=true)
*/
private $end_date;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Protagonist", inversedBy="events")
*/
private $protagonists;
/**
* #ORM\OneToMany(targetEntity="App\Entity\EventRegistration", mappedBy="event")
*/
private $eventRegistrations;
public function __construct()
{
$this->protagonists = new ArrayCollection();
$this->eventRegistrations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getStartDate(): ?\DateTimeInterface
{
return $this->start_date;
}
public function setStartDate(?\DateTimeInterface $start_date): self
{
$this->start_date = $start_date;
return $this;
}
public function getEndDate(): ?\DateTimeInterface
{
return $this->end_date;
}
public function setEndDate(?\DateTimeInterface $end_date): self
{
$this->end_date = $end_date;
return $this;
}
/**
* #return Collection|Protagonist[]
*/
public function getProtagonists(): Collection
{
return $this->protagonists;
}
public function addProtagonist(Protagonist $protagonist): self
{
if (!$this->protagonists->contains($protagonist)) {
$this->protagonists[] = $protagonist;
}
return $this;
}
public function removeProtagonist(Protagonist $protagonist): self
{
if ($this->protagonists->contains($protagonist)) {
$this->protagonists->removeElement($protagonist);
}
return $this;
}
/**
* #return Collection|EventRegistration[]
*/
public function getEventRegistrations(): Collection
{
return $this->eventRegistrations;
}
public function addEventRegistration(EventRegistration $eventRegistration): self
{
if (!$this->eventRegistrations->contains($eventRegistration)) {
$this->eventRegistrations[] = $eventRegistration;
$eventRegistration->setEvent($this);
}
return $this;
}
public function removeEventRegistration(EventRegistration $eventRegistration): self
{
if ($this->eventRegistrations->contains($eventRegistration)) {
$this->eventRegistrations->removeElement($eventRegistration);
// set the owning side to null (unless already changed)
if ($eventRegistration->getEvent() === $this) {
$eventRegistration->setEvent(null);
}
}
return $this;
}
}
I can access a collection of eventRegistrations with my Protagonist and Event entity, and I can access the protagonist and event with my EventRegistration entity.
The issue crops up when I try to create a checkbox with all the events available for the protagonist : I don't have any attribute that allows me to make a collection of those events :
ProtagonistType
<?php
namespace App\Form;
use App\Entity\Category;
use App\Entity\Event;
use App\Entity\EventRegistration;
use App\Entity\Protagonist;
use App\Entity\Tag;
use App\Repository\EventRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class ProtagonistType
* #package App\Form
*/
class ProtagonistType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
->add('japaneseName', TextType::class, [
'required' => false
])
->add('description', TextareaType::class)
->add('picture', TextType::class, [
'required' => false
])
->add('background', TextType::class, [
'required' => false
])
->add('isAlive', CheckboxType::class)
->add('category', EntityType::class, [
'class' => Category::class,
'choice_label' => 'title',
'expanded' => true,
'multiple' => false
])
->add('tags', EntityType::class, [
'class' => Tag::class,
'choice_label' => 'name',
'expanded' => true,
'multiple' => true,
'by_reference' => false,
])
**->add('eventRegistrations', CollectionType::class, [
'entry_type' => EventRegistrationType::class
])**
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Protagonist::class,
]);
}
}
EventRegistrationType :
<?php
namespace App\Form;
use App\Entity\Event;
use App\Entity\EventRegistration;
use App\Repository\EventRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class EventRegistrationType
* #package App\Form
*/
class EventRegistrationType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('event', EntityType::class, [
'class' => Event::class,
'choice_label' => 'name',
'multiple' => true,
'expanded' => true,
'by_reference' => false,
])
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => EventRegistration::class,
]);
}
}
The only effective solution I found is to create a ManyToMany relation between Protagonist and Event, then set another Registration table which is on a ManyToOne relation with Protagonist in order to get the protagonists registrations.
Still I'd like to make this many to many relation with extra fields works, I'm all ears for any solution you'd get to solve this issue.
Thank you!
Some theory
Entities are your models, business objects that have identities, data, behavior.
They are the heart, building blocks of your business model.
When we design entities - in the first place we should consider them as objects, that have their own shape and responsibilities instead of them being just containers for data stored in a database. Also, we should care about proper relations between entities.
Ideally, entities should always be valid. If so - they can be persisted at any time.
Persistence is a separate concern.
In general case, it's even not necessary for entities to be persisted into a database. They could just be persisted in memory, file system, key-value storage etc.
Forms is also a separate concern that is closer to working with user interfaces.
Forms help us to render user interface, translate requests from users to some structures of known shape that is easier to work with, than with raw data from requests, and validate this submitted data.
These structures are just containers for data retrieved from requests, they should not have any behavior.
These structures can be invalid at certain times.
What about the issue described?
So making entities playing roles of these forms underlying data structures might be not the best idea ever.
It's just clearly mixing of concerns and rigid coupling between different layers.
That is why you're having these issues.
So instead of using EventRegistration class as a data_class for EventRegistrationType and Protagonist - for ProtagonistType - consider creating separate data structures. Propagate submitted data to entities only when it's successfully validated.
Some useful links to read more on the topic (though the author calls underlying structures - commands somewhy):
Decoupling (Symfony2) Forms from Entities
Form, Command, and Model Validation
Other useful topics:
The Clean Architecture
Domain-Driven Design in PHP

automatically set database ID in CollectionType

I have two entities, ParkingType and Exception, there's a OneToMany relation between them, since each ParkingType can have multiple exceptions.
I made it so whenever I create a new ParkingType I can also create the exceptions related to it at the same time. I did that by using a CollectionType containing the exception form inside the parkingtype form. the collectiontype is dynamic so I can add as many exceptions as I would like.
Problem: the exception table has a column called type_id, which is used to relate that exception to the ParkingType and I have to populate that field myself everytime by choosing from a drop down. I don't want to do that, I want that field to reference the object that I just created by default.
My code:
Exception entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ExceptionRepository")
*/
class Exception
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=200, nullable=true)
*/
private $nom;
/**
* #ORM\Column(type="date", nullable=true)
*/
private $datedebut;
/**
* #ORM\Column(type="date", nullable=true)
*/
private $datefin;
/**
* #ORM\Column(type="time", nullable=true)
*/
private $tempsdebut;
/**
* #ORM\Column(type="time", nullable=true)
*/
private $tempsfin;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\TypeParking", inversedBy="exceptions")
* #ORM\JoinColumn(nullable=false)
*/
private $type;
public function getId(): ?int
{
return $this->id;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(?string $nom): self
{
$this->nom = $nom;
return $this;
}
public function getDatedebut(): ?\DateTimeInterface
{
return $this->datedebut;
}
public function setDatedebut(?\DateTimeInterface $datedebut): self
{
$this->datedebut = $datedebut;
return $this;
}
public function getDatefin(): ?\DateTimeInterface
{
return $this->datefin;
}
public function setDatefin(?\DateTimeInterface $datefin): self
{
$this->datefin = $datefin;
return $this;
}
public function getTempsdebut(): ?\DateTimeInterface
{
return $this->tempsdebut;
}
public function setTempsdebut(?\DateTimeInterface $tempsdebut): self
{
$this->tempsdebut = $tempsdebut;
return $this;
}
public function getTempsfin(): ?\DateTimeInterface
{
return $this->tempsfin;
}
public function setTempsfin(?\DateTimeInterface $tempsfin): self
{
$this->tempsfin = $tempsfin;
return $this;
}
public function getType(): ?TypeParking
{
return $this->type;
}
public function setType(?TypeParking $type): self
{
$this->type = $type;
return $this;
}
}
ParkingType Entity:
<?php
namespace App\Entity;
/**
* #ORM\Entity(repositoryClass="App\Repository\TypeParkingRepository")
*/
class TypeParking
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=55)
*/
private $libelle;
/**
* #ORM\Column(type="time", nullable=true)
*/
private $tempsmax;
/**
* #ORM\Column(type="date", nullable=true)
*/
private $jourdebut;
/**
* #ORM\Column(type="date", nullable=true)
*/
private $jourfin;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Exception", mappedBy="type", cascade={"persist"})
*/
private $exceptions;
public function __construct()
{
$this->exceptions = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getTempsmax(): ?\DateTimeInterface
{
return $this->tempsmax;
}
public function setTempsmax(\DateTimeInterface $tempsmax): self
{
$this->tempsmax = $tempsmax;
return $this;
}
public function getJourdebut(): ?\DateTimeInterface
{
return $this->jourdebut;
}
public function setJourdebut(\DateTimeInterface $jourdebut): self
{
$this->jourdebut = $jourdebut;
return $this;
}
public function getJourfin(): ?\DateTimeInterface
{
return $this->jourfin;
}
public function setJourfin(\DateTimeInterface $jourfin): self
{
$this->jourfin = $jourfin;
return $this;
}
public function getLibelle(): ?string
{
return $this->libelle;
}
public function setLibelle(string $libelle): self
{
$this->libelle = $libelle;
return $this;
}
/**
* #return Collection|Exception[]
*/
public function getExceptions(): Collection
{
return $this->exceptions;
}
public function removeException(Exception $exception): self
{
if ($this->exceptions->contains($exception)) {
$this->exceptions->removeElement($exception);
// set the owning side to null (unless already changed)
if ($exception->getType() === $this) {
$exception->setType(null);
}
}
return $this;
}
public function addException(Exception $exception)
{
$this->exceptions->add($exception);
}
}
ParkingType Form:
<?php
class TypeParkingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('libelle')
->add('tempsmax')
->add('jourdebut')
->add('jourfin')
->add('exceptions', CollectionType::class, [
'label' => 'Exception',
'entry_type' => Exception1Type::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'required' => false,
'by_reference' => true,
'delete_empty' => true,
'attr' => [
'class' => 'collection',
],
])
;
$builder->add('save', SubmitType::class, [
'label' => 'See my addresses',
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => TypeParking::class,
]);
}
}
Exception Form:
class ExceptionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nom')
->add('datedebut')
->add('datefin')
->add('tempsdebut')
->add('tempsfin')
->add('type',EntityType::class, [
'class' => TypeParking::class,
'choice_label' => 'libelle',
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Exception::class,
]);
}
}
just remove the field from the ExceptionType* and in your ParkingType, change the addException:
public function addException(Exception $exception)
{
$this->exceptions->add($exception);
$exception->setType($this); // <-- this is new.
}
update: you also have to set the exceptions CollectionType option by_reference to false, such that the adder is actually called by the form component.
this is one way to do it. the other option is to do this in your controller and call setType for every Exception you find in the ParkingType ...
*) this assumes, you don't edit the Exception on its own, ever. otherwise, either conditionally add the parking-type form field on certain options, or use a different form for Exceptions (for example named ParkingTypeExceptionType) that doesn't adds a form field for parking type.

exist and have public access in class "Symfony\Component\Form\FormView". Symfony 4

I've a problem about datas on my form view
The principle is the same as this video (https://www.youtube.com/watch?v=MRfsHix1eRA&t=1257s), that is to say: when I select a "management" I want the "services" associated to this "management".
These must be selected in a "report Form" (Report Entity).
OneOrMany service is linked to One management, and a report is linked to a management and a service.
Entities
Management.php
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ManagementRepository")
*/
class Management
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Service", mappedBy="management")
*/
private $services;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Report", mappedBy="management")
*/
private $reports;
public function __construct()
{
$this->services = new ArrayCollection();
$this->reports = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* #return Collection|Service[]
*/
public function getServices(): Collection
{
return $this->services;
}
public function addService(Service $service): self
{
if (!$this->services->contains($service)) {
$this->services[] = $service;
$service->setManagement($this);
}
return $this;
}
public function removeService(Service $service): self
{
if ($this->services->contains($service)) {
$this->services->removeElement($service);
// set the owning side to null (unless already changed)
if ($service->getManagement() === $this) {
$service->setManagement(null);
}
}
return $this;
}
/**
* #return Collection|Report[]
*/
public function getReports(): Collection
{
return $this->reports;
}
public function addReport(Report $report): self
{
if (!$this->reports->contains($report)) {
$this->reports[] = $report;
$report->setManagement($this);
}
return $this;
}
public function removeReport(Report $report): self
{
if ($this->reports->contains($report)) {
$this->reports->removeElement($report);
// set the owning side to null (unless already changed)
if ($report->getManagement() === $this) {
$report->setManagement(null);
}
}
return $this;
}
}
Service.php
<?php
namespace App\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ReportRepository")
*/
class Report
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="date")
*/
private $assigned_date;
/**
* #ORM\Column(type="decimal", precision=7, scale=2, nullable=true)
*/
private $time;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Game", inversedBy="reports")
*/
private $game;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Management", inversedBy="reports")
*/
private $management;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Service", inversedBy="reports")
*/
private $service;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Format", inversedBy="reports")
*/
private $format;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="reports")
* #ORM\JoinColumn(nullable=false)
*/
private $category;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="reports")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
public function getId(): ?int
{
return $this->id;
}
public function getAssignedDate(): ?\DateTimeInterface
{
return $this->assigned_date;
}
public function setAssignedDate(\DateTimeInterface $assigned_date): self
{
$this->assigned_date = $assigned_date;
return $this;
}
public function getTime()
{
return $this->time;
}
public function setTime($time): self
{
$this->time = $time;
return $this;
}
public function getGame(): ?Game
{
return $this->game;
}
public function setGame(?Game $game): self
{
$this->game = $game;
return $this;
}
public function getManagement(): ?Management
{
return $this->management;
}
public function setManagement(?Management $management): self
{
$this->management = $management;
return $this;
}
public function getService(): ?Service
{
return $this->service;
}
public function setService(?Service $service): self
{
$this->service = $service;
return $this;
}
public function getFormat(): ?Format
{
return $this->format;
}
public function setFormat(?Format $format): self
{
$this->format = $format;
return $this;
}
public function getCategory(): ?Category
{
return $this->category;
}
public function setCategory(?Category $category): self
{
$this->category = $category;
return $this;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
}
Report.php
<?php
namespace App\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ReportRepository")
*/
class Report
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="date")
*/
private $assigned_date;
/**
* #ORM\Column(type="decimal", precision=7, scale=2, nullable=true)
*/
private $time;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Game", inversedBy="reports")
*/
private $game;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Management", inversedBy="reports")
*/
private $management;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Service", inversedBy="reports")
*/
private $service;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Format", inversedBy="reports")
*/
private $format;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="reports")
* #ORM\JoinColumn(nullable=false)
*/
private $category;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="reports")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
public function getId(): ?int
{
return $this->id;
}
public function getAssignedDate(): ?\DateTimeInterface
{
return $this->assigned_date;
}
public function setAssignedDate(\DateTimeInterface $assigned_date): self
{
$this->assigned_date = $assigned_date;
return $this;
}
public function getTime()
{
return $this->time;
}
public function setTime($time): self
{
$this->time = $time;
return $this;
}
public function getGame(): ?Game
{
return $this->game;
}
public function setGame(?Game $game): self
{
$this->game = $game;
return $this;
}
public function getManagement(): ?Management
{
return $this->management;
}
public function setManagement(?Management $management): self
{
$this->management = $management;
return $this;
}
public function getService(): ?Service
{
return $this->service;
}
public function setService(?Service $service): self
{
$this->service = $service;
return $this;
}
public function getFormat(): ?Format
{
return $this->format;
}
public function setFormat(?Format $format): self
{
$this->format = $format;
return $this;
}
public function getCategory(): ?Category
{
return $this->category;
}
public function setCategory(?Category $category): self
{
$this->category = $category;
return $this;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
}
FormType
ReportType.php
<?php
namespace App\Form;
use App\Entity\Report;
use App\Entity\Game;
use App\Entity\Management;
use App\Entity\Service;
use App\Entity\Format;
use App\Entity\Category;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
class ReportType extends AbstractType
{
protected $tokenStorage;
public function __construct(TokenStorage $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$user = $this->tokenStorage->getToken()->getUser();
$builder->add('assigned_date', DateType::class, array(
'widget' => 'single_text',
// prevents rendering it as type="date", to avoid HTML5 date pickers
'html5' => false,
// adds a class that can be selected in JavaScript
'attr' => ['class' => 'js-datepicker'],
));
$builder->add('time');
$builder->add('game', EntityType::class, array(
'class' => Game::class,
'placeholder' => '',
'choice_label' => 'name'));
$builder->add('management', EntityType::class, array(
'class' => Management::class,
'placeholder' => 'Select a management',
'mapped' => false,
'choice_label' => 'name'));
$builder->get('management')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event)
{
$form = $event->getForm();
$form->getParent()->add('service',EntityType::class, array(
'class' => Service::class,
'placeholder' => 'Select a service',
'choices' => $form->getData()->getServices()
));
}
);
$builder->add('format', EntityType::class, array(
'class' => Format::class,
'placeholder' => '',
'choice_label' => 'name'));
$builder->add('category', EntityType::class, array(
'class' => Category::class,
'placeholder' => '',
'choice_label' => 'name'));
$builder->add('user', EntityType::class, array(
'class' => User::class,
'query_builder' => function (EntityRepository $er) {
$user = $this->tokenStorage->getToken()->getUser();
return $er->createQueryBuilder('u')
->Where('u.id='. $user->getId());
},
'choice_label' => 'id',
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Report::class,
]);
}
}
Twig associated
_form.twig.php
<?php
namespace App\Controller;
use App\Entity\Report;
use App\Form\ReportType;
use App\Repository\ReportRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/report")
*/
class ReportController extends AbstractController
{
/**
* #Route("/", name="report_index", methods="GET")
*/
public function index(ReportRepository $reportRepository): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$user = $this->getUser()->getId();
return $this->render('report/index.html.twig', ['reports' => $reportRepository->findAllByIdUser($user)]);
}
/**
* #Route("/new", name="report_new", methods="GET|POST")
*/
public function new(Request $request): Response
{
$report = new Report();
$form = $this->createForm(ReportType::class, $report);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($report);
$em->flush();
return $this->redirectToRoute('report_index');
}
return $this->render('report/new.html.twig', [
'report' => $report,
'form' => $form->createView(),
]);
}
/**
* #Route("/{id}", name="report_show", methods="GET")
*/
public function show(Report $report): Response
{
return $this->render('report/show.html.twig', ['report' => $report]);
}
/**
* #Route("/{id}/edit", name="report_edit", methods="GET|POST")
*/
public function edit(Request $request, Report $report): Response
{
$form = $this->createForm(ReportType::class, $report);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('report_edit', ['id' => $report->getId()]);
}
return $this->render('report/edit.html.twig', [
'report' => $report,
'form' => $form->createView(),
]);
}
/**
* #Route("/{id}", name="report_delete", methods="DELETE")
*/
public function delete(Request $request, Report $report): Response
{
if ($this->isCsrfTokenValid('delete'.$report->getId(), $request->request->get('_token'))) {
$em = $this->getDoctrine()->getManager();
$em->remove($report);
$em->flush();
}
return $this->redirectToRoute('report_index');
}
}
When I dump() my $form->getData()->getServices(), this return a collection. but i've this message : Neither the property "service" nor one of the methods "service()", "getservice()"/"isservice()"/"hasservice()" or "__call()" exist and have public access in class "Symfony\Component\Form\FormView".
on {{ form_widget(form.service, { 'attr': {'placeholder': "Service"} }) }}
Some people have a solution ?
I can provide more information if you need it
Thank you.

Categories