Unable to reverse value for property path […]: The choice […] does not exist or is not unique

After updating from Symfony 2.8 to 3.0 and verifying the operation, it was found that the author could not save due to a validation error.

Probably due to a specification change that reverses the Choice Type key and value.

I tried putting choice_label in the reference below, but it didn’t work.

I also tried array_flip $staffs, but the display changed and I couldn’t save it.

Is there any other way?

Symfony ChoiceType $choices – labels and values swapped

Error

This value is not valid.

Unable to reverse value for property path "author":
 The choice "9" does not exist or is not unique.

Before StaffChoicelist

use SymfonyComponentFormExtensionCoreChoiceListChoiceList;
use SymfonyComponentFormExtensionCoreChoiceListLazyChoiceList;

class StaffChoiceList extends LazyChoiceList
{
    public function __construct($staffService, $loginStaff)
    {
        $this->staffService = $staffService;
        $this->loginStaff = $loginStaff;
    }

    public function setCurrentStaff($currentStaff)
    {
        $this->currentStaff = $currentStaff;
    }

    public function loadChoiceList($value = null)
    {
        // Get the same shop staff as the login staff
        $staffs = $this->staffService->getStaffByShop($this->loginStaff->getShop());

        If the current staff is not included in the acquired staff (due to transfer etc.), add it to the end
        if ($this->currentStaff && !array_search($this->currentStaff, $staffs)) {
            $staffs[] = $this->currentStaff;
        }
            return new ChoiceList($staffs, $staffs);
    }
}

After StaffChoiceloader

use SymfonyComponentFormChoiceListLoaderChoiceLoaderInterface;
use SymfonyComponentFormChoicelistArrayChoiceList;

class StaffChoiceLoader implements ChoiceLoaderInterface
{
    public function loadChoiceList($value = null)
    {
        $staffs = $this->staffService->getStaffByShop($this->loginStaff->getShop());

        if ($this->currentStaff && !array_search($this->currentStaff, $staffs)) {
            $staffs[] = $this->currentStaff;
        }
            return new arrayChoiceList($staffs, null);
    }
    public function loadChoicesForValues(array $values, $value = null)
    {
        // Optimized when no data is preset
        if (empty($choices))
        {
            return array();
        }

        $values = array();
        foreach ($choices as $choice)
        {
            $values[] = (string) $this->loginStaff->getId();
        }

        return $values;
    }

    public function loadValuesForChoices(array $choices, $value = null)
    {
        // Optimized when nothing is sent
        if (empty($values))
        {
            return array();
        }

        // Get the entity from the ID and return the required data
        return $this->staffService->getStaffByShop($this->loginStaff->getShop());
    }
}

Type

        $authorChoiceList = new StaffChoiceLoader($this->staffService, $options['login_staff']);
        $builder->add("author", EntityType::class, array(
            "required" => true,
            "class" => "AppBundle:Staff",
            "choice_loader" => $authorChoiceList,
            "choice_label" =>function ($value) {
              return $value;
            },
        ));
        $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($authorChoiceList) {
            $article = $event->getData();
            $authorChoiceList->setCurrentStaff($article->getAuthor());
        });
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            "validation_groups" => function (FormInterface $form) {
                $article = $form->getData();
                return $this->getValidationGroups($article->getArticleStatus());
            },
        ));
    }

Staff.php

    /**
     * __toString
     *
     * @return string
     */
    public function __toString()
    {
        return $this->staffName;
    }
    /**
     * Set staffName
     *
     * @param string $staffName
     * @return Staff
     */
    public function setStaffName($staffName)
    {
        $this->staffName = $staffName;

        return $this;
    }

    /**
     * Get staffName
     *
     * @return string
     */
    public function getStaffName()
    {
        return $this->staffName;
    }

Article.php

    /**
     * @ORMManyToOne(targetEntity="Staff")
     * @ORMJoinColumn(name="author_id", referencedColumnName="id", nullable=true)
     */
    protected $author;
    /**
     * Set author
     *
     * @param AppBundleModelEntityStaff $author
     * @return Article
     */
    public function setAuthor(AppBundleModelEntityStaff $author = null)
    {
        $this->author = $author;

        return $this;
    }

    /**
     * Get author
     *
     * @return AppBundleModelEntityStaff
     */
    public function getAuthor()
    {
        return $this->author;
    }

Tried Code Type

    public function __construct($staffService, array $options = [])
    {
        $this->staffService = $staffService;
        $this->loginStaff = $options['login_staff'];
        $this->currentStaff = $options['login_staff'];
    }

Error

Notice: Undefined index: login_staff

Answer

Try make it simple first. If your ChoiceLoader doesn’t work, try to avoid it. After you get minimal working example you can refactor it.

EntityType is usefull only if you use QueryBuilder for obtaining choices from database.

Use ChoiceType as in example below:

use SymfonyComponentFormExtensionCoreTypeChoiceType;
use AppEntityStaff;

class StaffType extends AbstractType
{
    public function __construct($staffService)
    {
        $this->staffService = $staffService;
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $currentStaff = $options['currentStaff'];
        $loginStaff = $options['loginStaff'];
    
        $staff = $this->staffService->getStaffByShop($loginStaff->getShop());
        // If the current staff is not included in the acquired staff (due to transfer etc.), add it to the end
        if ($currentStaff && !array_search($currentStaff, $staffs)) {
            $staff[] = $currentStaff;
        }
    
        $builder->add('author', ChoiceType::class, [
            'choices' => $staffs,
            'choice_label' => function(Staff $employee, $key) {
                return $employee->getName();
            }
        ]);
    }
}

Staff array must contains unique keys e.g.:

$staff = [
    1 => new Staff(1),
    2 => new Staff(2),
    3 => new Staff(3),  
];

Build your form in controller with custom options:

$options = [
    'currentStaff' => $currentStaff,
    'loginStaff' => $loginStaff,
];
$this->createForm(StaffType::class, $data, $options);

For accessing $this->staffService use dependency injection in your form type. And for $this->currentStaff and $this->loginStaff use $options.

Does it works for you? If not, please provide structure of $staff array.