Symfony 2.3升级2.8时调用DefaultChoiceListFactory未定义getChoices报错
Symfony 2.3 升级至2.8 ChoiceList适配问题
场景描述
- 项目当前正从Symfony 2.3版本升级至2.8,提前适配后续升级3.0需要处理的废弃特性。
- 参照官方2.7版本升级文档的指引,重写了
Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList和Symfony\Component\Form\Extension\Core\ChoiceList\LazyChoiceList相关逻辑,开发过程中触发了如下报错。 - 最初误以为是缺少
getChoices()方法,于是在ChoiceLoader.php中补充了该方法,但报错依然存在,需要定位问题根因及解决方案。 - 已知
choice_list配置项后续会被废弃,已规划对这部分逻辑做整体重写。
报错信息
Attempted to call an undefined method named "getChoices" of class "Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory"
原始代码片段
自定义ChoiceLoader实现(文件路径:ChoiceList/ChoiceLoader.php)
namespace Ahi\Sp\AdminBundle\Form\ChoiceList; // 移除旧依赖 //use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList; //use Symfony\Component\Form\Extension\Core\ChoiceList\LazyChoiceList; // 引入新组件依赖 use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory; use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; class StaffChoiceLoader implements ChoiceLoaderInterface { private $staffService; private $loginStaff; private $currentStaff; public function __construct($staffService, $loginStaff) { $this->staffService = $staffService; $this->loginStaff = $loginStaff; } 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 ChoiceList($staffs, $staffs); // 原新逻辑(存在错误) return new DefaultChoiceListFactory($staffs, $staffs); } public function loadChoicesForValues(array $values, $value = null) { if (empty($choices)) { return array(); } $values = array(); foreach ($choices as $person) { $values[] = (string) $staff->getId(); } return $values; } public function loadValuesForChoices(array $choices, $value= null) { if (empty($values)) { return array(); } return $this->staffService->getStaffByShop($this->loginStaff->getShop()); } // 原补充的空方法 public function getChoices() { } }
表单类型配置(文件路径:ArticleType.php)
// 移除旧实例化逻辑 //$authorChoiceList = new StaffChoiceLoader($this->staffService, $options['login_staff']); // 原新逻辑(存在错误) $factory = new DefaultChoiceListFactory(); $authorChoiceList = $factory->createListFromLoader(new StaffChoiceLoader($this->staffService, $options['login_staff'])); $builder->add("author", "entity", array( "required" => true, "class" => "AhiSpCommonBundle:Staff", "choice_list" => $authorChoiceList, "empty_value" => "Please select", ));
问题根因与修正方案
报错的核心原因是对新版本ChoiceList组件的API调用逻辑完全错位:
DefaultChoiceListFactory是用来生成ChoiceList实例的工厂类,本身没有选择项列表、也没有getChoices方法,不能被当作具体的ChoiceList实现类直接实例化传入列表参数。之前补在自定义Loader类上的getChoices()方法属于无效代码,和Factory类内部的调用逻辑没有关联,自然解决不了报错。- 多个接口实现方法存在变量名不匹配的低级错误,会在后续流程中触发更多未定义变量的报错。
- 原代码中判断员工是否存在于列表的逻辑使用
array_search有误:当当前员工是数组的第一个元素时,array_search会返回键值0,!0的判断结果为true,会重复追加同一个员工。
具体修正步骤
- 删掉自定义Loader类里空的
getChoices()方法,该方法不属于ChoiceLoaderInterface的强制实现范围。 - 在
loadChoiceList方法里,直接返回系统提供的ArrayChoiceList实例,不要手动实例化工厂类;给Loader类加一个缓存属性,避免每次调用都重新查询数据库加载员工列表。 - 修正两个接口实现方法的变量匹配问题,直接复用已加载的ChoiceList实例的匹配方法,不要重复写查询逻辑。
- 表单配置里不要手动实例化工厂生成choice_list,直接把自定义Loader传给
choice_loader配置项即可,移除已经废弃的choice_list配置;把empty_value改成新组件规范的placeholder配置项,避免后续触发废弃告警。 - 把原列表判断逻辑改成
in_array的严格匹配,避免重复追加员工。
修正后可直接使用的代码
修正后的StaffChoiceLoader
namespace Ahi\Sp\AdminBundle\Form\ChoiceList; use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; use Symfony\Component\Form\ChoiceList\ArrayChoiceList; class StaffChoiceLoader implements ChoiceLoaderInterface { private $staffService; private $loginStaff; private $currentStaff; private $cachedChoiceList; public function __construct($staffService, $loginStaff) { $this->staffService = $staffService; $this->loginStaff = $loginStaff; } public function loadChoiceList($value = null) { // 命中缓存直接返回,避免重复查询 if (null !== $this->cachedChoiceList) { return $this->cachedChoiceList; } $staffs = $this->staffService->getStaffByShop($this->loginStaff->getShop()); // 严格判断是否存在,避免误判 if ($this->currentStaff && !in_array($this->currentStaff, $staffs, true)) { $staffs[] = $this->currentStaff; } return $this->cachedChoiceList = new ArrayChoiceList($staffs); } public function loadChoicesForValues(array $values, $value = null) { if (empty($values)) { return []; } // 复用已加载列表的匹配逻辑 return $this->loadChoiceList($value)->getChoicesForValues($values); } public function loadValuesForChoices(array $choices, $value = null) { if (empty($choices)) { return []; } // 复用已加载列表的匹配逻辑 return $this->loadChoiceList($value)->getValuesForChoices($choices); } }
修正后的ArticleType.php表单配置
$builder->add("author", "entity", array( "required" => true, "class" => "AhiSpCommonBundle:Staff", // 直接传入自定义Loader,无需手动生成choice_list "choice_loader" => new StaffChoiceLoader($this->staffService, $options['login_staff']), "placeholder" => "Please select", ));
内容的提问来源于stack exchange,提问作者go_live
相关产品推荐
相关产品推荐

