此案例是一个案例研究,我正在尝试解决此问题,以便向学生解释如何组织实体和创建表单。
我在3个实体之间具有这种奇异的关系:
主角 EventRegistration 事件
由于EventRegistration表中有一些列,因此无法转换成多对多关系:
主角:
<?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;
}
}
事件:
<?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;
}
}
我可以通过我的主角和事件实体访问eventRegistrations的集合,并且我可以通过我的EventRegistration实体访问主角和事件。
当我尝试创建一个包含所有可用于主角的事件的复选框时,问题浮出水面:我没有任何属性可以让我收集这些事件的集合:
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,
]);
}
}
我发现的唯一有效的解决方案是在主角和事件之间创建一个ManyToMany关系,然后在与主角的ManyToOne关系上设置另一个注册表,以获取主角的注册。
仍然希望与额外的字段建立这种多对多的联系,我非常想方设法解决此问题。
谢谢!
实体是您的模型,具有标识,数据和行为的业务对象。
它们是您的心,是您的业务模式的基础。
设计实体时,首先应将它们视为具有其自身形状和职责的对象,而不仅仅是将其存储为数据库中数据的容器。另外,我们应该关心实体之间的适当关系。
理想情况下,实体应始终有效。如果是这样,则可以随时保留它们。
Persistence是一个单独的问题。
通常,甚至没有必要将实体持久化到数据库中。它们可以只保存在内存,文件系统,键值存储等中。
Forms也是一个单独的关注点,更接近于使用用户界面。
表单帮助我们呈现用户界面,将用户的请求转换为比已知请求中的原始数据更易于使用的某些已知形状的结构,并验证提交的数据。
这些结构只是从请求中检索数据的容器,它们不应有任何行为。
这些结构在某些时候可能是无效的。
因此,让实体扮演这些形式的底层数据结构角色可能不是最好的主意。
这显然是关注点和不同层之间的刚性耦合的混合。
这就是您遇到这些问题的原因。
因此,不要将EventRegistration
类用作data_class
和EventRegistrationType
-对于Protagonist
的ProtagonistType
-考虑创建单独的数据结构。仅当成功验证提交的数据后,才将其传播给实体。
一些有用的链接,可以阅读更多有关该主题的内容(尽管作者称其为底层结构-某些命令):
[其他有用的主题: