I think your approach is problematic. The Form component is meant to modify the object passed to it, which is - as far as I can tell - not what you want. You don't want to modify a Pagerfanta
object, you want to select entities for bulk actions.
So to solve your problem, the very very raw things that have to happen: A <form>
must be displayed on the page with a checkbox for every entry that's a candidate for the bulk action, with some button(s) to trigger the bulk action(s).
Your form - besides the entry for checkboxes - is alright I guess and not really your problem, as far as I can tell. You're not even interested in editing the Pagerfanta
object (I hope) and just want the selection. To do that, we provide the collection of objects that are queued to be displayed on the page to the form via an option, and then use that option to build the field (read: pass the collection to the EntityType field).
Adding the collection to the form (call) as an option:
Somewhere in your controller, you should have something like:
$form = $this->createForm(ModelEntitySelectionType::class, $pagerfanta);
Change this to:
$form = $this->createForm(ModelEntitySelectionType::class, [], [
'model_choices' => $pagerfanta->getCurrentPageResults(),
]);
the method getCurrentPageResults
return the collection of entities for the current page (obviously). The empty array []
as the second parameter is ultimately the object/array you're trying to edit/create. I've chosen an array here, but you can also make it a new action class (like a DTO) e.g. ModelBulkAction
with properties: model
and action
:
class ModelBulkAction {
public $model;
public $action;
}
Note these kinds of objects only make sense if used in more than one place - then the call would be:
$form = $this->createForm(ModelEntitySelectionType::class, new ModelBulkAction(), [
'model_choices' => $pagerfanta->getCurrentPageResults(),
]);
Pass the choices to the sub form:
The Form component will complain, if you provide an option to a form, which doesn't expect that option. That's the purpose of AbstractType::configureOptions(OptionsResolver $resolver)
. (side note: I don't know, what your setDefaultOptions
is supposed to achieve, tbh, with an ExceptionInterface
nonetheless. No clue, really).
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,
]);
}
and finally actually passing the collection to the entity type sub form:
// 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,
])
// ...
;
Also, your data mapping is not needed any more and should be removed.
Adding the form widgets to the output
This is pretty much similar to the Stack Overflow question and answer you linked. However, the keys in the form are different, because my approach is slightly different:
{{ 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) }}
(note: this will probably show the id of the entry next to the checkbox, since that's your choice_label
, I believe this can be removed by: {{ form_widget(form.model[index], {label:false}) }}
or alternatively by setting the choice_label
to false
instead of 'id'
).
Getting your bulk entities
After $form->handleRequest($request);
you can check for submission and the form values:
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 ...
}
If you implemented the ModelBulkAction
approach, $data
is an object of that kind and has $data->model
as well as $data->action
for you to use (or pass on to a repository).
More stuff
Obviously the model_choices
option can be named almost any way you like (but should not clash with existing options the AbstractType
may have).
To make an entity selectable (besides the checkbox), you can for example use <label for="{{ form.model[index].vars.id }}"><!-- something to click --></label>
as a non-javascript approach (may add styling). With js it's pretty much irrelevant because you probably just need to select the first checkbox in the row.
Alternatives
Alternative to providing the collection of objects to the form, you could theoretically also provide a list of ids and use the ChoiceType
instead of the EntityType
. There is nothing to be gained from this though.