Symfony有关“自定义窗体渲染”的文档看起来模糊且容易让人误解。我试图在窗体上渲染一个自定义元素,但是当尝试这样做时,我看到了莫名其妙的行为。Symfony 3覆盖默认窗体渲染
问题:
我希望呈现一个多列选择框。功能等价物被描述准确位置: jQuery Get Selected Option From Dropdown
语境
- 模型 - 我有一个用户实体。用户可以属于零个,一个或多个组织。
- 控制器 - 我的控制器进行API调用,该调用获取用户和组织(如果有用户属于)以及用户可以添加的组织列表。
- 查看 - 我正在渲染一个创建/编辑窗体Twig模板显示所有用户字段以及用户可能订阅的组织列表。有两个选择框并排。其中显示用户可以属于组织,另一种是用户可以添加可能组织名单:
<select "user_orgs">
<option value="1">Organization 1</option>
</select>
<button id="add"> << </button>
<button id="remove"> << </button>
<select "available_orgs">
<option value="1">Organization 1</option>
<option value="2">Organization 1</option>
</select>
我使用了“bootstrap3_form_horizontal.html.twig模板形式呈现。为了捕捉上述功能,我需要提供一种方式来上面的HTML的一个变种添加到我的形式。
解决方案1
向Twig扩展添加一个方法,该方法可以从窗体调用并呈现html。
- (正)最简单,最有效的
- (负)不使用树枝。鸭子类型需要形成值。
- (负面)不是非常干燥。
对于所有的解决方案,我已经创建了一个AbstractType提供的形式呈现控件:
//Predefined form types:
use Symfony\Component\Form\Extension\Core\Type;
class UserType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('email', Type\EmailType::class, [
'label' => 'Email/Username',
])
. . .
->add('organizations', Type\ChoiceType::class, [
'label' => 'Organizations',
'selections' => $options['organizations'],
'multiple' => true,
])
->add('submit', Type\SubmitType::class, [
'attr' => ['class' => 'btn-success btn-outline'],
]);
}
}
user_create.html.twig:
{% form_theme form _self %}
{% block body %}
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form.email) }}
{{ multi_select(form.organizations, selected_organizations)|raw }}
{{ form_end(form) }}
{% endblock %}
“多选择” 是一个自定义的嫩枝扩展名:
class CustomTwigExtension extends \Twig_Extension {
public function getFunctions() {
return array(
new \Twig_SimpleFunction('multi_select', [$this, 'multiSelect']),
}
public function multiSelect(\Symfony\Component\Form\FormView $formView, $selections) {
$formData = $formView->vars;
$formLabel = $formData['label'];
$selectName = $formData['full_name'];
$formId = $formData['id'];
$optionsId = $formId . '_options';
$rhsOptions = "";
$lhsOptions = "";
foreach($selections as $key => $value) {
$lhsOptions .= '<option value="' . $value . ' selected">' . $key . '</option>';
}
foreach($formData['choices'] as $option) {
$rhsOptions .= '<option value="' . $option->value . ' selected">' . $option->label . '</option>';
}
$html = <<<HTML
<div class="form-group">
<label class="col-sm-2 control-label required" for="$formId">$formLabel</label>
<div class="col-sm-2">
<select id="$formId" class="form-control" name="$selectName" multiple>
$lhsOptions
</select>
</div>
<div class="col-sm-1" style="width: 4%;">
<button class="btn btn-default" id="m-select-to">«</button>
<br /> <br />
<button class="btn btn-default" id="m-select-from">»</button>
</div>
<div class="col-sm-2">
<select id="$optionsId" class="form-control" multiple>
$rhsOptions
</select>
</div>
</div>
HTML;
return $html;
}
不是很ele gant,但它会呈现所需的结果。 (我将HEREDOC html包含在我的最终结果中更精确)。
的问题是,在“form_start”枝杈方法也呈现的组织除了从上述控制一个选择框 - 我已指示它做的是通过在用户的抽象类型设置“ChoiceType”。
形式大概类似于: (相关部分被注释括号)
<form name="user" method="post" action="/admin/users/save/" class="form-horizontal">
<!-- THIS IS THE CUSTOM GENERATED CONTROL -->
<div class="form-group">
<label class="col-sm-2 control-label required" for="user_organizations">Organizations</label>
<div class="col-sm-2">
<select id="user_organizations" class="form-control" name="user[organizations][]" multiple="">
</select>
</div>
<!-- END CUSTOM GENERATED CONTROL -->
<div class="col-sm-1" style="width: 4%;">
<button class="btn btn-default" id="m-select-to">«</button>
<br> <br>
<button class="btn btn-default" id="m-select-from">»</button>
</div>
<div class="col-sm-2">
<select id="user_organizations_options" class="form-control" multiple="">
<option value="1 selected">Organization1</option>
<option value="2 selected">Organization2</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label required" for="user_email">Email/Username</label>
<div class="col-sm-10">
<input id="user_email" name="user[email]" required="required" class="form-control" type="email">
</div>
<!-- THIS IS THE SYMFONY GENERATED CONTROL -->
<div class="form-group">
<label class="col-sm-2 control-label required" for="user_organizations">Organizations</label>
<div class="col-sm-10">
<select id="user_organizations" name="user[organizations][]" class="form-control" multiple="multiple">
<option value="1 selected">Organization1</option>
<option value="2 selected">Organization2</option>
</select>
</div>
</div>
<!-- END SYMFONY GENERATED CONTROL -->
</div><input id="user__token" name="user[_token]" value="imatoken" type="hidden"></form>
正如你所看到的,有两种不同的事情在进行。通用的“开箱即用”选择标签和我自定义的标签。
form_start方法似乎遍历UserType的子项并呈现除了指定的控件之外的控件。
解决方案2
创建一个自定义模板,以控制形式呈现。
- (正)如下记载的最佳做法为实现我的结果
- (负)不完全记录。不起作用。
继http://symfony.com/doc/current/form/form_customization.html概括和阐明了SO后的程序: How to create a custom Symfony2 Twig form template block 除了: Custom form field template with twig
我做了以下内容:
创建自定义类型从渲染自定义模板(来自Symfony文档链接):
自定义类型: AppBundle \ Form \ MultiSelectType:
class MultiSelectType extends AbstractType {
public function buildView(FormView $view, FormInterface $form, array $options) {
$view->vars['selections'] = $this->setOptions($options['selections']);
//The following lines will assign the select box containing
//already defined organizations.
$entity = $view->parent->vars['value'];
$field = $options['entity_field_name'];
$method = 'get' . $field;
$values = call_user_func(array($entity, $method));
$view->vars['choices'] = $values;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'selections' => array(),
'custom_label' => 'slapping juice'
));
}
private function setOptions($options) {
$ar = [];
foreach($options as $k => $v) {
$ar[] = ['value' => $v, 'label' => $k];
}
return $ar;
}
}
我几乎不知道该在这里指定什么。我可以覆盖哪些方法? Symfony文档敦促您查看类似的实例。选择类型类是800行!我是否需要800多行PHP才能呈现20个HTML?
我引用它在我的用户类型正是如此:
class UserType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
. . .
$builder->add('organizations', MultiSelectType::class, [
'label' => 'Organizations',
'selections' => $options['organizations'],
'entity_field_name' => 'organizations',
])
. . .
}
}
在我的枝条扩展,我添加了一个参考
(通过魔术,因为在该Symfony的文档没有提到这上面链接。)类CBMSHelperExtension扩展\ {Twig_Extension
public function getFunctions() {
return array(
new \Twig_SimpleFunction('multiselect_widget', null, [
'node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode',
'is_safe' => [ 'html' ]])
);
}
Aaaaand Finaly,我几乎完全自定义模板,复制 - 按照指示 - 从现有的树枝模板:
资源/视图/形式/ fields.html.twig
{% use "form_div_layout.html.twig" %}
{% use "bootstrap_3_horizontal_layout.html.twig" %}
{% block multi_select_widget %}
{% spaceless %}
<div class="form-group">
{{ form_label(form) }}
<div class="{{ block('form_group_class') }}">
<select {{ block('widget_attributes') }} multiple="multiple">
{%- set options = selections -%}
{% for choice in options %}
<option value="{{ choice.value }}" selected="selected">{{ choice.label }}</option>
{% endfor %}
</select>
</div>
<div class="{{ block('form_group_class') }}">
{% for organization in choices %}
<p>{{ organization.name }}</p>
{% endfor %}
</div>
</div>
{% endspaceless %}
{% endblock %}
(加入上述给config.yml)
现在的问题是类似于上面的一个。
有一个额外的“组织”标签被渲染!我就饶你的HTML转储,但基本上它做什么是这样的:
|| form tag ||
| form group: |
| label - email | input box for email |
| form group: |
| label - organizations | form group |
| | label - organizations |
| | custom template |
这是试图来呈现它自己的组织,即使我在我的权力做的一切,告诉它不要。
问题
我如何能实现自定义表单呈现以呈现从HTML自定义表单模板下面的Symfony的“最佳实践“没有埋头苦干,或创建一个不可扩展的解决方案?
红利问题: 仅仅是我还是在Symfony中开发像试图构建Rube Goldberg设备?
此链接可能会对您有所帮助:http://symfony.com/doc/current/bundles/SensioGeneratorBundle/index.html#overriding-skeleton-templates –