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.