Some years have passed and the setting shines up again:
I have a ManyToMany related Entity that I want to filter for using the LexikFormFilterBundle. But I also want to allow to filter explicitly for Entities that have no such related Entity. In my case I want to allow to filter for ToDos that are assigned to some specific Users, but allow also to filter for ToDos that are not assigned at all. So the problems begin.
See also that issue at LexikFormFilterBundle
My solution for now is, indeed, switching to a ChoiceType::class and that looks like this:
// We are in a buildForm function of a Filter-Form extending
// Symfony\Component\Form\AbstractType and using The LexikFormFilterBundle
// prepare the choices with a "none"-choice on top
// feel free to add also a "all"-choice, if that is needed
$myChoices = [
'None of those' => 'none',
];
foreach ($this->myWhateverRepository->getMyChoices() as $myChoice) {
// where $myChoice is some related Entity
$myChoices[$myChoice->getIdentifyingName()] = $myChoice->getId();
}
/*
anyhow, we now we have an array like this:
[
'None of those' => 'none',
'One related Entity' => 2,
'Another related Entity' => 4,
]
*/
$builder
->add('relatedWhatevers', ChoiceFilterType::class, [
'choices' => $myChoices,
'multiple' => true, // or false, but then the closure for apply_filter must be implemented in simpler way
'label' => 'Filter for related whatevers',
'required' => false,
'apply_filter' => function (QueryInterface $filterQuery, string $field, array $values): ?ConditionInterface {
if (empty($values['value'])) {
// nothing to filter here
return null;
}
// for multiple=true make sure, the $value is an ArrayCollection,
// as the ChoiceFilterType gives us a plain array
// that does not work in the LexikFormFilterBundle at least until 7.0.3
// otherwise just take that one value for multiple=false
$value = new ArrayCollection($values['value']);
$whatevers = $value->filter(function(int|string $v) { return 'none' !== $v; });
// join the related field and don't forget to do that as an
// innerJoin, otherwise the isNull() doesn't find anything
$queryAlias = 'myWhatevers';
$filterQuery->getQueryBuilder()->innerJoin($field, $queryAlias);
// for multiple=false that construct can be reduced to last two variants
if ($value->contains('none')) {
if ($whatevers->count() > 0) {
// we have 'none' and at least one other value to filter for
return $filterQuery->createCondition(
$filterQuery->getExpr()->orX(
$filterQuery->getExpr()->isNull($queryAlias),
$filterQuery->getExpr()->in($queryAlias . '.id', ':p_whatevers')
),
['p_whatevers' => $whatevers],
);
}
return $filterQuery->createCondition(
// we only have 'none', so the filter is less complex
$filterQuery->getExpr()->isNull($queryAlias),
[],
);
}
return $filterQuery->createCondition(
// no 'null' selected, so we just use that standard expression for related entities
$filterQuery->getExpr()->in($queryAlias . '.id', ':p_whatevers'),
['p_whatevers' => $whatevers],
);
},
])
;
And that works: I can find ToDos assigned to some Users and/or are assigned to nobody. And when I don't fill the field, the filter does not do anything.
query_builderoption to filter entities?query_builderdefines the queries, that limit the entities listed to chose in the widget. I need, on top of that, some more options/choices that are not entities, but other values that the controller (or in my case the filter logic) can use.EntityType::classform extension workaround but I'm not sure what do you needs exactly.