以批量形式使用 Pagerfanta/ 非 ArrayAccess 列表

Using a Pagerfanta/ non ArrayAccess list in a bulk form

我正在使用 here 提供的解决方案将批量操作的复选框添加到 CRUD 列表。

但是,我的结果是用 Pagerfanta 分页的,所以我似乎需要在我的表单中使用 DataMapper。

我尝试了各种解决方案,但无法使所选字段在我的表单数据中可用:

class ModelEntitySelectionType extends AbstractType  implements DataMapperInterface
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('model_id', EntityType::class, [
            'required' => false,
            'class' => ModelFile::class,
            'choice_label' => 'id',
            'property_path' => '[id]', # in square brackets!
            'multiple' => true,
            'expanded' => true
        ])
            ->add('action', ChoiceType::class, [
                'choices' => [
                    'Delete' => 'delete'
                ]
            ])
            ->add('submit', SubmitType::class, [
                'label' => 'Process'
            ])

        ->setDataMapper($this)
        ;


    }

    public function setDefaultOptions(ExceptionInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => null,
            'csrf_protection' => false
        ));
    }

    public function mapDataToForms($data, $forms)
    {
        // there is no data yet, so nothing to prepopulate
        if (null === $data) {
            return;
        }

        $formData = [];

        /** @var FormInterface[] $forms */
        $forms = iterator_to_array($forms);
        $forms['model_id']->setData($formData);

    }

    public function mapFormsToData($forms, &$data)
    {
        //$forms = iterator_to_array($forms);
        $data = [
            'model_id' => iterator_to_array($data)
            ];
    }

缺少的部分是当我使用调试器调查 mapFormsToData 时:

我明白我必须如何 "loop" 通过 PagerFanta 对象,因为它没有 ArrayAccess,但是实际勾选了哪些复选框的数据在哪里?此外,我的其他表单字段(操作)无法在此处访问

我觉得你的做法是有问题的。 Form 组件旨在修改传递给它的对象,据我所知,这不是您想要的。您不想修改 Pagerfanta 个对象,您想要 select 个实体进行批量操作。

因此,为了解决您的问题,必须发生非常非常原始的事情:必须在页面上显示 <form>,并且每个条目都有一个复选框,用于批量操作的候选条目,以及一些按钮(s) 触发批量操作。

我猜你的表单 - 除了复选框条目 - 没问题,据我所知并不是你的问题。您甚至对编辑 Pagerfanta 对象不感兴趣(我希望如此)而只想要 selection。为此,我们通过选项将排队显示在页面上的对象集合提供给表单,然后使用该选项构建字段(阅读:将集合传递给 EntityType 字段)。

将集合添加到表单(调用)作为选项:

有些东西在你的控制器里,你应该有这样的东西:

$form = $this->createForm(ModelEntitySelectionType::class, $pagerfanta);

将其更改为:

$form = $this->createForm(ModelEntitySelectionType::class, [], [
    'model_choices' => $pagerfanta->getCurrentPageResults(),
]);

方法 getCurrentPageResults return 当前页面的实体集合(很明显)。作为第二个参数的空数组 [] 最终是您要 edit/create 的 object/array。我在这里选择了一个数组,但您也可以将其设为新操作 class(如 DTO),例如ModelBulkAction 具有属性:modelaction:

class ModelBulkAction {
    public $model;
    public $action;
}

请注意,这些类型的对象只有在多个地方使用时才有意义。 那么呼叫将是:

$form = $this->createForm(ModelEntitySelectionType::class, new ModelBulkAction(), [
    'model_choices' => $pagerfanta->getCurrentPageResults(),
]);

将选项传递给子表单:

如果您向表单提供一个选项,而表单组件不期望该选项,则表单组件会报错。这就是 AbstractType::configureOptions(OptionsResolver $resolver) 的目的。 (旁注:我不知道,你的 setDefaultOptions 应该实现什么,说实话,尽管如此 ExceptionInterface。真的没有线索。)

public function configureOptions(OptionsResolver $resolver) {
    $resolver->setRequired([
         'model_choices', // adds model_choices as a REQUIRED option!
    ]);
    $resolver->setDefaults([
         // change null to ModelBulkAction::class, if applicable
         'data_class' => null, 
    ]);
}

最后实际将集合传递给实体类型子表单:

    // in ModelEntitySelectionType::buildForm($builder, $options)

    $builder->add('model', EntityType::class, [
        'required' => false,
        'class' => ModelFile::class,
        'choice_label' => 'id',
        'choices' => $options['model_choices'], // set the choices explicitly
        'multiple' => true,
        'expanded' => true,
    ])
    // ...
    ;

此外,您的数据映射不再需要,应将其删除。

将表单小部件添加到输出

这与您链接的 Whosebug 问题和答案非常相似。但是,表单中的键是不同的,因为我的做法略有不同:

{{ form_start(form) }}
{% for entity in pagerfanta %}

    {# stuff before the checkbox #}
    {{ form_widget(form.model[entity.id]) }}
    {# stuff after the checkbox #}

{% endfor %}
{# place the selection of action somewhere! and possibly the "submit" button #}
{{ form_widget(form.action) }} 
{{ form_end(form) }}

(注意:这可能会显示复选框旁边条目的 ID,因为那是你的 choice_label,我相信可以通过以下方式删除它:{{ form_widget(form.model[index], {label:false}) }} 或者通过设置 choice_labelfalse 而不是 'id').

获取批量实体

$form->handleRequest($request); 之后您可以检查提交和表单值:

if($form->isSubmitted() && $form->isValid()) {
    $data = $form->getData();
    // $data['model'] contains an array of entities, that were selected
    // $data['action'] contains the selection of the action field

    // do the bulk action ...
}

如果您实现了 ModelBulkAction 方法,$data 就是那种类型的对象,并且有 $data->model 以及 $data->action 供您使用(或传递给到存储库)。

更多内容

显然 model_choices 选项几乎可以按照您喜欢的任何方式命名(但不应与 AbstractType 可能具有的现有 options 冲突)。

要使实体 select 可用(除了复选框),您可以使用 <label for="{{ form.model[index].vars.id }}"><!-- something to click --></label> 作为非 javascript 方法(可以添加样式)。对于 js,它几乎无关紧要,因为您可能只需要 select 行中的第一个复选框。

备选方案

除了向表单提供对象集合之外,理论上您还可以提供 ID 列表并使用 ChoiceType instead of the EntityType。但这并没有什么好处。

希望对您有所帮助。