I'm new with Symfony. I've followed a couple of tutorials trying to learn dependent selects, so I use a DestinationFormType and a DestinationController to save a Destination. The JS part works well, but now when I try to save the entity throws the next exception:
Argument 1 passed to Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader::getIdValue() must be an object or null, int given, called in \vendor\symfony\form\ChoiceList\ArrayChoiceList.php on line 200
The weird part is that it seems to be trying to convert a value (191) that need no conversion (stack call top):
The error is thrown for the country entity. This is my DestinationFormType
<?php
namespace App\Form;
use App\Entity\City;
use App\Entity\Country;
use App\Entity\Destination;
use App\Entity\DestinationCategory;
use App\Entity\DestinationSubcategory;
use App\Entity\Region;
use App\Entity\State;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DestinationFormType extends AbstractType
{
/** @var EntityManagerInterface */
private $em;
/**
* @param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('coordinates')
->add('kidsCost')
->add('adultsCost')
->add('description')
;
$builder->addEventListener(FormEvents::PRE_SET_DATA, array($this, 'onPreSetData'));
$builder->addEventListener(FormEvents::PRE_SUBMIT, array($this, 'onPreSubmit'));
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Destination::class
]);
}
/**
* @param FormEvent $event
*/
public function onPreSubmit(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
/** @var Region $region */
$region = $this->em
->getRepository(Region::class)
->find($data['region']);
/** @var Country $country */
$country = $this->em
->getRepository(Country::class)
->find($data['country']);
/** @var State $state */
$state = $this->em
->getRepository(State::class)
->find($data['state']);
/** @var City $city */
$city = $this->em
->getRepository(City::class)
->find($data['city']);
/** @var DestinationCategory $city */
$category = $this->em
->getRepository(DestinationCategory::class)
->find($data['category']);
/** @var DestinationCategory $city */
$subcategory = $this->em
->getRepository(DestinationSubcategory::class)
->find($data['subcategory']);
$this->addLocationDropdowns($form, $region, $country, $state, $city);
$this->addCategoryDropdowns($form, $category, $subcategory);
}
/**
* @param FormEvent $event
*/
public function onPreSetData(FormEvent $event) {
/** @var Destination $destination */
$destination = $event->getData();
$form = $event->getForm();
$category = $destination && $destination->getCategory() ? $destination->getCategory() : null;
$subcategory = $destination && $destination->getSubcategory() ? $destination->getSubcategory() : null;
$region = $destination && $destination->getRegion() ? $destination->getRegion() : null;
$country = $destination && $destination->getCountry() ? $destination->getCountry() : null;
$state = $destination && $destination->getState() ? $destination->getState() : null;
$city = $destination && $destination->getCity() ? $destination->getCity() : null;
$this->addLocationDropdowns($form, $region, $country, $state, $city);
$this->addCategoryDropdowns($form, $category, $subcategory);
}
/**
* @param FormInterface $form
* @param Region|null $region
* @param Country|null $country
* @param State|null $state
* @param City|null $city
*/
private function addLocationDropdowns(FormInterface $form, ?Region $region, ?Country $country, ?State $state, ?City $city) {
$this->addRegionDropDown($form, $region);
$this->addCountryDropdown($form, $region, $country);
$this->addStateDropdown($form, $country, $state);
$this->addCityDropdown($form, $state, $city);
}
/**
* @param FormInterface $form
* @param Region|null $region
*/
private function addRegionDropDown(FormInterface $form, ?Region $region)
{
$form->add('region', EntityType::class,[
'required' => true,
'data' => $region,
'placeholder' => 'Select a Region...',
'class' => Region::class
]);
}
/**
* @param FormInterface $form
* @param Region|null $region
* @param Country|null $country
*/
private function addCountryDropdown(FormInterface $form, ?Region $region, ?Country $country)
{
$countries = array();
if ($region) {
$countryRepository = $this->em->getRepository(Country::class);
$countries = $countryRepository->findByRegionId($region->getId());
}
$form->add('country', EntityType::class, [
'required' => true,
'data' => $country,
'placeholder' => 'Select a Region first ...',
'class' => Country::class,
'choices' => $countries
]);
}
/**
* @param FormInterface $form
* @param Country|null $country
* @param State|null $state
*/
private function addStateDropdown(FormInterface $form, ?Country $country, ?State $state)
{
$states = array();
if ($country) {
$stateRepository = $this->em->getRepository(State::class);
$states = $stateRepository->findByCountryId($country->getId());
}
$form->add('state', EntityType::class, [
'required' => true,
'data' => $state,
'placeholder' => 'Select a Country first ...',
'class' => State::class,
'choices' => $states
]);
}
/**
* @param FormInterface $form
* @param State|null $state
* @param City|null $city
*/
private function addCityDropdown(FormInterface $form, ?State $state, ?City $city)
{
$cities = array();
if ($state) {
$cityRepository = $this->em->getRepository(City::class);
$cities = $cityRepository->findByStateId($state->getId());
}
$form->add('city', EntityType::class, [
'required' => true,
'data' => $city,
'placeholder' => 'Select a State first ...',
'class' => State::class,
'choices' => $cities
]);
}
/**
* @param FormInterface $form
* @param DestinationCategory|null $category
* @param DestinationSubcategory|null $subcategory
*/
private function addCategoryDropdowns(FormInterface $form, ?DestinationCategory $category, ?DestinationSubcategory $subcategory)
{
$this->addCategoryDropDown($form, $category);
$this->addSubcategoryDropDown($form, $category, $subcategory);
}
/**
* @param FormInterface $form
* @param DestinationCategory|null $category
*/
private function addCategoryDropDown(FormInterface $form, ?DestinationCategory $category)
{
$form->add('category', EntityType::class,[
'required' => true,
'data' => $category,
'placeholder' => 'Select a Category...',
'class' => DestinationCategory::class
]);
}
/**
* @param FormInterface $form
* @param DestinationCategory|null $category
* @param DestinationSubcategory|null $subcategory
*/
private function addSubcategoryDropDown(FormInterface $form, ?DestinationCategory $category, ?DestinationSubcategory $subcategory)
{
$subcategories = array();
if ($category) {
$countryRepository = $this->em->getRepository(DestinationSubcategory::class);
$subcategories = $countryRepository->findByCategoryId($category->getId());
}
$form->add('subcategory', EntityType::class, [
'required' => true,
'data' => $subcategory,
'placeholder' => 'Select a Category first ...',
'class' => DestinationSubcategory::class,
'choices' => $subcategories
]);
}
}
This is my DestinationController
<?php
namespace App\Controller;
use App\Entity\Destination;
use App\Form\DestinationFormType;
use App\Repository\DestinationRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DestinationController extends AbstractController
{
/**
* @Route("admin/destination/new", name="admin_destination_new")
*
* @param EntityManagerInterface $em
* @param Request $request
*
* @return Response
*/
public function new(EntityManagerInterface $em, Request $request)
{
$form = $this->createForm(DestinationFormType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var Destination $destination */
$destination = $form->getData();
$em->persist($destination);
$em->flush();
$this->addFlash('success', 'Destination created');
$this->redirectToRoute('admin_destination_list');
}
return $this->render('destination/new.html.twig', [
'destForm' => $form->createView()
]);
}
/**
* @Route("admin/destination/{id}/edit", name="admin_destination_edit")
*
* @param Destination $destination
* @param EntityManagerInterface $em
* @param Request $request
*
* @return Response
*/
public function edit(Destination $destination, EntityManagerInterface $em, Request $request)
{
$form = $this->createForm(DestinationFormType::class, $destination);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($destination);
$em->flush();
$this->addFlash('success', 'Destiny updated');
$this->redirectToRoute('admin_destination_list');
}
return $this->render('destination/edit.html.twig', [
'destForm' => $form->createView()
]);
}
/**
* @Route("admin/destination", name="admin_destination_list")
* @param DestinationRepository $destRepo
*
* @return Response
*/
public function list(DestinationRepository $destRepo){
/** @var Destination $destinations */
$destinations = $destRepo->findAll();
return $this->render('destination/list.html.twig', [
'destinations' => $destinations
]);
}
}
And this is my Destination entity
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
use Gedmo\Timestampable\Traits\TimestampableEntity;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* @ORM\Entity(repositoryClass="App\Repository\DestinationRepository")
*/
class Destination
{
use TimestampableEntity;
use SoftDeleteableEntity;
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=150)
*/
private $name;
/**
* @ORM\Column(type="string", length=150, nullable=true)
*/
private $coordinates;
/**
* @ORM\Column(type="string", length=150, unique=true)
* @Gedmo\Slug(fields={"name"})
*/
private $slug;
/**
* @ORM\Column(type="decimal", precision=8, scale=2, nullable=true)
*/
private $kidsCost;
/**
* @ORM\Column(type="decimal", precision=8, scale=2, nullable=true)
*/
private $adultsCost;
/**
* @ORM\ManyToMany(targetEntity="App\Entity\Package", mappedBy="destinations")
*/
private $packages;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $description;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $address;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\City")
* @ORM\JoinColumn(nullable=false)
*/
private $city;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\State")
* @ORM\JoinColumn(nullable=false)
*/
private $state;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Region", inversedBy="destinations")
* @ORM\JoinColumn(nullable=false)
*/
private $region;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Country", inversedBy="destinations")
* @ORM\JoinColumn(nullable=false)
*/
private $country;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\DestinationCategory", inversedBy="destinations")
* @ORM\JoinColumn(nullable=false)
*/
private $category;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\DestinationSubcategory", inversedBy="destinations")
* @ORM\JoinColumn(nullable=false)
*/
private $subcategory;
public function __construct()
{
$this->packages = 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 getCoordinates(): ?string
{
return $this->coordinates;
}
public function setCoordinates(?string $coordinates): self
{
$this->coordinates = $coordinates;
return $this;
}
public function getSlug(): ?string
{
return $this->slug;
}
public function setSlug(string $slug): self
{
$this->slug = $slug;
return $this;
}
public function getKidsCost(): ?string
{
return $this->kidsCost;
}
public function setKidsCost(?string $kidsCost): self
{
$this->kidsCost = $kidsCost;
return $this;
}
public function getAdultsCost(): ?string
{
return $this->adultsCost;
}
public function setAdultsCost(?string $adultsCost): self
{
$this->adultsCost = $adultsCost;
return $this;
}
/**
* @return Collection|Package[]
*/
public function getPackages(): Collection
{
return $this->packages;
}
public function addPackage(Package $package): self
{
if (!$this->packages->contains($package)) {
$this->packages[] = $package;
$package->addDestination($this);
}
return $this;
}
public function removePackage(Package $package): self
{
if ($this->packages->contains($package)) {
$this->packages->removeElement($package);
$package->removeDestination($this);
}
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
public function getAddress(): ?string
{
return $this->address;
}
public function setAddress(?string $address): self
{
$this->address = $address;
return $this;
}
public function getCity(): ?City
{
return $this->city;
}
public function getState(): ?State
{
return $this->getCity() ? $this->getCity()->getState() : null;
}
public function getCountry(): ?Country
{
return $this->getCity()? $this->getState()->getCountry() : null;
}
public function getRegion() : ?Region
{
return $this->getCity()? $this->getCountry()->getRegion() : null;
}
public function setCity(?City $city): self
{
$this->city = $city;
return $this;
}
public function setState(?State $state): self
{
$this->state = $state;
return $this;
}
public function setRegion(?Region $region): self
{
$this->region = $region;
return $this;
}
public function setCountry(?Country $country): self
{
$this->country = $country;
return $this;
}
public function getCategory(): ?DestinationCategory
{
return $this->category;
}
public function setCategory(?DestinationCategory $category): self
{
$this->category = $category;
return $this;
}
public function getSubcategory(): ?DestinationSubcategory
{
return $this->subcategory;
}
public function setSubcategory(?DestinationSubcategory $subcategory): self
{
$this->subcategory = $subcategory;
return $this;
}
}
Can someone point me to the right direction to solve this?
Ps. I'm using symfony 5.0.7.
Thanks in advance
Well, the answer wasn't obvious by watching my code, cause when you see
$countries = $countryRepository->findByRegionId($region->getId());
You may think is returning an array of countries but in fact is a helper for JS tha only returns id and name
/**
* @param int $regionId
* @return array
*/
public function findByRegionId(int $regionId)
{
return $this->createQueryBuilder('c')
->select(['c.id', 'c.name'])
->where('c.region = :id')
->orderBy('c.name')
->setParameter('id', $regionId)
->getQuery()
->execute();
}
so just in case someone else struggle with this: $choices is expecting an array of objects, not an array with id and name, so I modified my addCountryDropDown method like this
private function addCountryDropdown(FormInterface $form, ?Region $region, ?Country $country)
{
$countries = array();
if ($region) {
$countryRepository = $this->em->getRepository(Country::class);
$countries = $countryRepository->findBy([
'region' => $region->getId()
]);
}
$form->add('country', EntityType::class, [
'required' => true,
'data' => $country,
'placeholder' => 'Select a Region first ...',
'class' => Country::class,
'choices' => $countries
]);
}