Symfony 2.8 动态 ChoiceType 选项
Symfony 2.8 dynamic ChoiceType options
在我的项目中,我有一些带有选择类型的表单,其中 很多 选项。
所以我决定构建一个基于 jquery 自动完成的自动完成选择类型,它在运行时向原始 <select>
添加新的 <option>
HTML 元素。选择后它们会被正确提交,但无法在默认值 ChoicesToValuesTransformer
中处理,因为在我创建表单时它们不存在。
如何让 symfony 接受我动态添加的值?
我找到了这个答案 Validating dynamically loaded choices in Symfony 2 ,其中提交的值用于修改 PRE_SUBMIT
表单事件中的表单,但对于我的情况 运行 无法得到它。我需要更改当前类型已知的选项,而不是向表单添加新的小部件
一个基本的(可能不是最好的)选项是取消映射您表单中的字段,例如:
->add('field', choiceType::class, array(
...
'mapped' => false
))
在控制器中,验证后,获取数据并将它们发送给实体,如下所示:
$data = request->request->get('field');
// OR
$data = $form->get('field')->getData();
// and finish with :
$entity = setField($data);
要处理动态添加的值,请使用选择类型的 'choice_loader'
选项。 It's new in symfony 2.7 遗憾的是根本没有任何文档。
基本上它是一个实现 ChoiceLoaderInterface
的服务,它定义了三个函数:
loadValuesForChoices(array $choices, $value = null)
- 在构建表单时调用并接收绑定到表单中的对象的预设值
loadChoiceList($value = null)
- 在构建视图中被调用并且应该return一般选择的完整列表
loadChoicesForValues(array $values, $value = null)
- 在表单提交时调用并接收提交的数据
现在的想法是在选择加载程序中将 ArrayChoiceList
保持为私有 属性。在调用构建表单 loadValuesForChoices(...)
时,我们将所有预设选项添加到我们的选项列表中,以便将它们显示给用户。构建视图 loadChoiceList(...)
被调用,但我们不加载任何东西,我们只是 return 我们之前创建的私人选择列表。
现在用户与表单交互,一些额外的选择通过自动完成加载并放入 HTML。在提交表单时,将提交选定的值,在我们的控制器操作中,首先创建表单,然后在 $form->handleRequest(..)
loadChoicesForValues(...)
上调用,但提交的值可能与包含在中的值完全不同开始。因此,我们将内部选择列表替换为一个仅包含提交值的新列表。
我们的表单现在可以完美保存自动完成添加的数据。
棘手的部分是,每当我们使用表单类型时,我们都需要一个新的选择加载器实例,否则内部选择列表将混合所有选择。
由于目标是编写一个新的自动完成选择类型,您通常会使用依赖注入将您的选择加载器传递到类型服务中。
但是对于类型来说,如果你总是需要一个新的实例,这是不可能的,我们必须通过选项来包含它。在默认选项中设置选择加载器不起作用,因为它们也被缓存了。要解决该问题,您必须编写一个匿名函数,该函数需要将选项作为参数:
$resolver->setDefaults(array(
'choice_loader' => function (Options $options) {
return AutocompleteFactory::createChoiceLoader();
},
));
编辑:
这是选择加载器的简化版本 class:
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
class AutocompleteChoiceLoader implements ChoiceLoaderInterface
{
/** @var ChoiceListInterface */
private $choiceList;
public function loadValuesForChoices(array $choices, $value = null)
{
// is called on form creat with $choices containing the preset of the bound entity
$values = array();
foreach ($choices as $key => $choice) {
// we use a DataTransformer, thus only plain values arrive as choices which can be used directly as value
if (is_callable($value)) {
$values[$key] = (string)call_user_func($value, $choice, $key);
}
else {
$values[$key] = $choice;
}
}
// this has to be done by yourself: array( label => value )
$labeledValues = MyLabelService::getLabels($values);
// create internal choice list from loaded values
$this->choiceList = new ArrayChoiceList($labeledValues, $value);
return $values;
}
public function loadChoiceList($value = null)
{
// is called on form view create after loadValuesForChoices of form create
if ($this->choiceList instanceof ChoiceListInterface) {
return $this->choiceList;
}
// if no values preset yet return empty list
$this->choiceList = new ArrayChoiceList(array(), $value);
return $this->choiceList;
}
public function loadChoicesForValues(array $values, $value = null)
{
// is called on form submit after loadValuesForChoices of form create and loadChoiceList of form view create
$choices = array();
foreach ($values as $key => $val) {
// we use a DataTransformer, thus only plain values arrive as choices which can be used directly as value
if (is_callable($value)) {
$choices[$key] = (string)call_user_func($value, $val, $key);
}
else {
$choices[$key] = $val;
}
}
// this has to be done by yourself: array( label => value )
$labeledValues = MyLabelService::getLabels($values);
// reset internal choice list
$this->choiceList = new ArrayChoiceList($labeledValues, $value);
return $choices;
}
}
在我的项目中,我有一些带有选择类型的表单,其中 很多 选项。
所以我决定构建一个基于 jquery 自动完成的自动完成选择类型,它在运行时向原始 <select>
添加新的 <option>
HTML 元素。选择后它们会被正确提交,但无法在默认值 ChoicesToValuesTransformer
中处理,因为在我创建表单时它们不存在。
如何让 symfony 接受我动态添加的值?
我找到了这个答案 Validating dynamically loaded choices in Symfony 2 ,其中提交的值用于修改 PRE_SUBMIT
表单事件中的表单,但对于我的情况 运行 无法得到它。我需要更改当前类型已知的选项,而不是向表单添加新的小部件
一个基本的(可能不是最好的)选项是取消映射您表单中的字段,例如:
->add('field', choiceType::class, array(
...
'mapped' => false
))
在控制器中,验证后,获取数据并将它们发送给实体,如下所示:
$data = request->request->get('field');
// OR
$data = $form->get('field')->getData();
// and finish with :
$entity = setField($data);
要处理动态添加的值,请使用选择类型的 'choice_loader'
选项。 It's new in symfony 2.7 遗憾的是根本没有任何文档。
基本上它是一个实现 ChoiceLoaderInterface
的服务,它定义了三个函数:
loadValuesForChoices(array $choices, $value = null)
- 在构建表单时调用并接收绑定到表单中的对象的预设值
loadChoiceList($value = null)
- 在构建视图中被调用并且应该return一般选择的完整列表
loadChoicesForValues(array $values, $value = null)
- 在表单提交时调用并接收提交的数据
现在的想法是在选择加载程序中将 ArrayChoiceList
保持为私有 属性。在调用构建表单 loadValuesForChoices(...)
时,我们将所有预设选项添加到我们的选项列表中,以便将它们显示给用户。构建视图 loadChoiceList(...)
被调用,但我们不加载任何东西,我们只是 return 我们之前创建的私人选择列表。
现在用户与表单交互,一些额外的选择通过自动完成加载并放入 HTML。在提交表单时,将提交选定的值,在我们的控制器操作中,首先创建表单,然后在 $form->handleRequest(..)
loadChoicesForValues(...)
上调用,但提交的值可能与包含在中的值完全不同开始。因此,我们将内部选择列表替换为一个仅包含提交值的新列表。
我们的表单现在可以完美保存自动完成添加的数据。
棘手的部分是,每当我们使用表单类型时,我们都需要一个新的选择加载器实例,否则内部选择列表将混合所有选择。
由于目标是编写一个新的自动完成选择类型,您通常会使用依赖注入将您的选择加载器传递到类型服务中。 但是对于类型来说,如果你总是需要一个新的实例,这是不可能的,我们必须通过选项来包含它。在默认选项中设置选择加载器不起作用,因为它们也被缓存了。要解决该问题,您必须编写一个匿名函数,该函数需要将选项作为参数:
$resolver->setDefaults(array(
'choice_loader' => function (Options $options) {
return AutocompleteFactory::createChoiceLoader();
},
));
编辑: 这是选择加载器的简化版本 class:
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
class AutocompleteChoiceLoader implements ChoiceLoaderInterface
{
/** @var ChoiceListInterface */
private $choiceList;
public function loadValuesForChoices(array $choices, $value = null)
{
// is called on form creat with $choices containing the preset of the bound entity
$values = array();
foreach ($choices as $key => $choice) {
// we use a DataTransformer, thus only plain values arrive as choices which can be used directly as value
if (is_callable($value)) {
$values[$key] = (string)call_user_func($value, $choice, $key);
}
else {
$values[$key] = $choice;
}
}
// this has to be done by yourself: array( label => value )
$labeledValues = MyLabelService::getLabels($values);
// create internal choice list from loaded values
$this->choiceList = new ArrayChoiceList($labeledValues, $value);
return $values;
}
public function loadChoiceList($value = null)
{
// is called on form view create after loadValuesForChoices of form create
if ($this->choiceList instanceof ChoiceListInterface) {
return $this->choiceList;
}
// if no values preset yet return empty list
$this->choiceList = new ArrayChoiceList(array(), $value);
return $this->choiceList;
}
public function loadChoicesForValues(array $values, $value = null)
{
// is called on form submit after loadValuesForChoices of form create and loadChoiceList of form view create
$choices = array();
foreach ($values as $key => $val) {
// we use a DataTransformer, thus only plain values arrive as choices which can be used directly as value
if (is_callable($value)) {
$choices[$key] = (string)call_user_func($value, $val, $key);
}
else {
$choices[$key] = $val;
}
}
// this has to be done by yourself: array( label => value )
$labeledValues = MyLabelService::getLabels($values);
// reset internal choice list
$this->choiceList = new ArrayChoiceList($labeledValues, $value);
return $choices;
}
}