I'm having trouble understanding what I'm doing wrong here. I have a "Project" entity that has a relation (ManyToMany) with another entity, "Service". In turn, Service is related to a "Task" entity, which is itself related to a "Section" entity. In the ProjectType I create a form field using FormBuilder for the property that identifies the Project - Service relation. In the twig template I'm trying to access Service's properties. I can't seem to access the property "section" of the task property in Service.
Project entity
<?php
/**
* App_Entity_Project
*/
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Eleva\BackofficeBundle\Entity\BaseEntity;
use App\Repository\ProjectRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* Class Project
*
* Main Project entity.
* @author Eleva
* @package App\Entity
* @license © 2023 Eleva SRL
* @ORM\Entity(repositoryClass=ProjectRepository::class)
*/
class Project extends BaseEntity
{
/**
* @var int|null $id
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* @ORM\Column(type="string", length=255)
*/
private $name;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $soReferral;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $notes;
/**
* @ORM\ManyToOne(targetEntity=Customer::class, inversedBy="projects")
* @ORM\JoinColumn(nullable=false)
*/
private $customer;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $pm;
/**
* @ORM\Column(type="string", length=45, nullable=true)
*/
private $description;
/**
* @ORM\ManyToMany(targetEntity=Service::class, inversedBy="projects")
*/
private $services;
/**
* @ORM\OneToMany(targetEntity=ProjectProduct::class, mappedBy="project", cascade={"persist"})
*/
private $projectProducts;
public function __construct()
{
$this->services = new ArrayCollection();
$this->projectProducts = new ArrayCollection();
}
/**
* getId
*
* Returns the id value.
*/
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 getSoReferral(): ?string
{
return $this->soReferral;
}
public function setSoReferral(?string $soReferral): self
{
$this->soReferral = $soReferral;
return $this;
}
public function getNotes(): ?string
{
return $this->notes;
}
public function setNotes(?string $notes): self
{
$this->notes = $notes;
return $this;
}
public function getCustomer(): ?Customer
{
return $this->customer;
}
public function setCustomer(?Customer $customer): self
{
$this->customer = $customer;
return $this;
}
public function getPm(): ?string
{
return $this->pm;
}
public function setPm(?string $pm): self
{
$this->pm = $pm;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
/**
* @return Collection<int, Service>
*/
public function getServices(): Collection
{
return $this->services;
}
public function addService(Service $service): self
{
if (!$this->services->contains($service)) {
$this->services[] = $service;
}
return $this;
}
public function removeService(Service $service): self
{
$this->services->removeElement($service);
return $this;
}
/**
* @return Collection<int, ProjectProduct>
*/
public function getProjectProducts(): Collection
{
return $this->projectProducts;
}
public function addProjectProduct(ProjectProduct $projectProduct): self
{
if (!$this->projectProducts->contains($projectProduct)) {
$this->projectProducts[] = $projectProduct;
$projectProduct->setProject($this);
}
return $this;
}
public function removeProjectProduct(ProjectProduct $projectProduct): self
{
if ($this->projectProducts->removeElement($projectProduct)) {
// set the owning side to null (unless already changed)
if ($projectProduct->getProject() === $this) {
$projectProduct->setProject(null);
}
}
return $this;
}
}
Service entity
<?php
/**
* App_Entity_Service
*/
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Eleva\BackofficeBundle\Entity\BaseEntity;
use App\Repository\ServiceRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* Class Service
*
* Main Service entity.
* @author Eleva
* @package App\Entity
* @license © 2023 Eleva SRL
* @ORM\Entity(repositoryClass=ServiceRepository::class)
*/
class Service extends BaseEntity
{
/**
* @var int|null $id
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $description;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $estimatedTime;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $estimatedTimeAway;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $numberOfTechnicians;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $timeAwayTechnicians;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $totalEstimatedHours;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $actualTime;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $hoursAway;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $actualHoursAway;
/**
* @ORM\ManyToOne(targetEntity=JobPosition::class, inversedBy="services")
*/
private $jobPosition;
/**
* @ORM\ManyToMany(targetEntity=Project::class, mappedBy="services")
*/
private $projects;
/**
* @ORM\ManyToOne(targetEntity=Task::class, inversedBy="services")
*/
private $task;
/**
* @ORM\Column(type="string", length=45)
*/
private $overtime;
public function __construct()
{
$this->projects = new ArrayCollection();
}
/**
* getId
*
* Returns the id value.
*/
public function getId(): ?int
{
return $this->id;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
public function getEstimatedTime(): ?int
{
return $this->estimatedTime;
}
public function setEstimatedTime(?int $estimatedTime): self
{
$this->estimatedTime = $estimatedTime;
return $this;
}
public function getEstimatedTimeAway(): ?int
{
return $this->estimatedTimeAway;
}
public function setEstimatedTimeAway(?int $estimatedTimeAway): self
{
$this->estimatedTimeAway = $estimatedTimeAway;
return $this;
}
public function getNumberOfTechnicians(): ?int
{
return $this->numberOfTechnicians;
}
public function setNumberOfTechnicians(?int $numberOfTechnicians): self
{
$this->numberOfTechnicians = $numberOfTechnicians;
return $this;
}
public function getTimeAwayTechnicians(): ?int
{
return $this->timeAwayTechnicians;
}
public function setTimeAwayTechnicians(?int $timeAwayTechnicians): self
{
$this->timeAwayTechnicians = $timeAwayTechnicians;
return $this;
}
public function getTotalEstimatedHours(): ?int
{
return $this->totalEstimatedHours;
}
public function setTotalEstimatedHours(?int $totalEstimatedHours): self
{
$this->totalEstimatedHours = $totalEstimatedHours;
return $this;
}
public function getActualTime(): ?int
{
return $this->actualTime;
}
public function setActualTime(?int $actualTime): self
{
$this->actualTime = $actualTime;
return $this;
}
public function getHoursAway(): ?int
{
return $this->hoursAway;
}
public function setHoursAway(?int $hoursAway): self
{
$this->hoursAway = $hoursAway;
return $this;
}
public function getActualHoursAway(): ?int
{
return $this->actualHoursAway;
}
public function setActualHoursAway(?int $actualHoursAway): self
{
$this->actualHoursAway = $actualHoursAway;
return $this;
}
public function getJobPosition(): ?JobPosition
{
return $this->jobPosition;
}
public function setJobPosition(?JobPosition $jobPosition): self
{
$this->jobPosition = $jobPosition;
return $this;
}
/**
* @return Collection<int, Project>
*/
public function getProjects(): Collection
{
return $this->projects;
}
public function addProject(Project $project): self
{
if (!$this->projects->contains($project)) {
$this->projects[] = $project;
$project->addService($this);
}
return $this;
}
public function removeProject(Project $project): self
{
if ($this->projects->removeElement($project)) {
$project->removeService($this);
}
return $this;
}
public function getTask(): ?Task
{
return $this->task;
}
public function setTask(?Task $task): self
{
$this->task = $task;
return $this;
}
public function getOvertime(): ?string
{
return $this->overtime;
}
public function setOvertime(string $overtime): self
{
$this->overtime = $overtime;
return $this;
}
}
Task entity
<?php
/**
* App_Entity_Task
*/
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Eleva\BackofficeBundle\Entity\BaseEntity;
use App\Repository\TaskRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* Class Task
*
* Main Task entity.
* @author Eleva
* @package App\Entity
* @license © 2023 Eleva SRL
* @ORM\Entity(repositoryClass=TaskRepository::class)
*/
class Task extends BaseEntity
{
/**
* @var int|null $id
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* @ORM\Column(type="string", length=255)
*/
private $name;
/**
* @ORM\ManyToOne(targetEntity=ServiceSection::class, inversedBy="tasks")
* @ORM\JoinColumn(nullable=false)
*/
private $section;
/**
* @ORM\OneToMany(targetEntity=Service::class, mappedBy="task", cascade={"persist"})
*/
private $services;
public function __construct()
{
$this->services = new ArrayCollection();
}
/**
* getId
*
* Returns the id value.
*/
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 getSection(): ?ServiceSection
{
return $this->section;
}
public function setSection(?ServiceSection $section): self
{
$this->section = $section;
return $this;
}
/**
* @return Collection<int, Service>
*/
public function getServices(): Collection
{
return $this->services;
}
public function addService(Service $service): self
{
if (!$this->services->contains($service)) {
$this->services[] = $service;
$service->setTask($this);
}
return $this;
}
public function removeService(Service $service): self
{
if ($this->services->removeElement($service)) {
// set the owning side to null (unless already changed)
if ($service->getTask() === $this) {
$service->setTask(null);
}
}
return $this;
}
}
ServiceSection entity
<?php
/**
* App_Entity_ServiceSection
*/
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Eleva\BackofficeBundle\Entity\BaseEntity;
use App\Repository\ServiceSectionRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* Class ServiceSection
*
* Main ServiceSection entity.
* @author Eleva
* @package App\Entity
* @license © 2023 Eleva SRL
* @ORM\Entity(repositoryClass=ServiceSectionRepository::class)
*/
class ServiceSection extends BaseEntity
{
/**
* @var int|null $id
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* @ORM\Column(type="string", length=255)
*/
private $name;
/**
* @ORM\OneToMany(targetEntity=Task::class, mappedBy="section")
*/
private $tasks;
public function __construct()
{
$this->tasks = new ArrayCollection();
}
/**
* getId
*
* Returns the id value.
*/
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<int, Task>
*/
public function getTasks(): Collection
{
return $this->tasks;
}
public function addTask(Task $task): self
{
if (!$this->tasks->contains($task)) {
$this->tasks[] = $task;
$task->setSection($this);
}
return $this;
}
public function removeTask(Task $task): self
{
if ($this->tasks->removeElement($task)) {
// set the owning side to null (unless already changed)
if ($task->getSection() === $this) {
$task->setSection(null);
}
}
return $this;
}
}
ProjectType excerpt
$serviceBuilder = $builder->create('services', ServiceType::class, [
'required' => false,
'by_reference' => true,
'data_class' => Service::class
]);
ServiceType excerpt
$builder->add('task', EntityType::class, [
'required' => false,
'class' => \App\Entity\Task::class,
'multiple' => false,
'expanded' => false,
'mapped' => true,
'choice_label' => 'name',
'empty_data' => '',
'constraints' => [],
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->where('u.del = 0 ')
->orderBy('u.id', 'DESC');
},
'row_attr' => [
'class' => 'container_task_id',
'id' => 'container_task_id'
],
'attr' => [
'class' => 'selectize',
'data-field-column' => 'task_id',
'data-field-name' => 'task'
]
]);
TaskType excerpt
$builder->add('section', EntityType::class, [
'required' => false,
'class' => \App\Entity\ServiceSection::class,
'multiple' => false,
'expanded' => false,
'mapped' => true,
'choice_label' => 'name',
'empty_data' => '',
'constraints' => [],
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->where('u.del = 0 ')
->orderBy('u.id', 'DESC');
},
'row_attr' => [
'class' => 'container_service_section_id',
'id' => 'container_service_section_id'
],
'attr' => [
'class' => 'selectize',
'data-field-column' => 'service_section_id',
'data-field-name' => 'section'
]
]);
What I'm trying to do is render the section property of task as a dropdown
<div class="flex-col col-1-2 container_service_section_id">
<h6 class="required">
{{ 'BACKOFFICE_SERVICE_SECTION' |trans }}
</h6>
<div class="field-container {% if form_errors(form.services.task.section) is not empty %}alert-field{% endif %}">
{{ form_widget(form.services.task.section) }}
<span class="alert">
{{ form_errors(form.services.task.section) ?: 'EMPTY_FIELD' | trans }}
</span>
</div>
</div>
but this is the error I'm getting:
Neither the property "section" nor one of the methods "section()", "getsection()"/"issection()"/"hassection()" or "__call()" exist and have public access in class "Symfony\Component\Form\FormView".
Am I completely stupid and I'm missing something obvious or did I do everything wrong?