传递给 Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader::getIdValue() 的参数 1 必须是对象或 null,给定的 int

Argument 1 passed to Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader::getIdValue() must be an object or null, int given

我是 Symfony 新手。我已经按照 couple of tutorials 尝试学习依赖选择,所以我使用 DestinationFormType 和 DestinationController 来保存 Destination。 JS 部分运行良好,但现在当我尝试保存实体时抛出下一个异常:

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

奇怪的是,它似乎试图转换一个不需要转换的值 (191)(堆栈调用顶部):

针对国家/地区实体抛出错误。这是我的 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
        ]);
    }
}

这是我的 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
        ]);
    }
}

这是我的目标实体

<?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;
    }
}

有人能指出我解决这个问题的正确方向吗?

Ps。我正在使用 symfony 5.0.7.

提前致谢

嗯,看我的代码答案并不明显,因为当你看到

$countries = $countryRepository->findByRegionId($region->getId());

您可能认为返回的是国家/地区数组,但实际上它只是 JS 的助手,只有 returns id 和 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();
}

以防万一其他人遇到这个问题:$choices 需要一个对象数组,而不是一个带有 id 和名称的数组,所以我修改了我的 addCountryDropDown 方法,如下所示

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
    ]);
}

迁移到带有用户实体的 Symfony 5.4 后,我遇到了同样的错误。 现在 getRoles() 函数必须 return 角色列表作为字符串。 在我的例子中,我有一个实体角色。

我通过在用户实体中添加 getRolesObjects() 解决了这个问题:

public function getRolesObjects(): array
{
    if(is_array($this->roles)) {
        return $this->roles;
    } else if($this->roles && ($this->roles instanceof Collection)) {
        return $this->roles->toArray();
    } else {
        return [];
    }
}

并在 FormType 中添加自定义 getter:

$builder
            ->add('firstName', TextType::class)
            ->add('lastName', TextType::class)
            ->add('email',  TextType::class)
            ->add('roles', EntityType::class, [
                'class' => Role::class,
                'multiple' => true,

                'getter' => function (User $user, FormInterface $form): array {
                    return $user->getRolesObjects();
                }

            ]);