3

I have a filter form in Symfony, which can filter for an Entity. For this purpose, I have a field with an EntityFilterType (Lexik\Bundle\FormFilterBundle\Filter\Form\Type\EntityFilterType), which simply extends Symfony's builtin EntityType.

Now I want to add an "all" and a "none" option to this EntityType. If it was a ChoiceType, I would simply change the choices array, but the EntityType only accepts valid Entity-IDs as its value on submit and also only Entities in the array given to the 'choices' option.

My question is: How can I add additional options to an EntityType form field? Besides the ugly way to reimplement the Entity-stuff into a ChoiceType field? Any ideas on this? Am I missing a documented way?

Greets, spackmat

11
  • You needs something more that query_builder option to filter entities? Commented Oct 6, 2016 at 13:18
  • The query_builder defines 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. Commented Oct 6, 2016 at 13:55
  • 1
    Well, I'm thinking about EntityType::class form extension workaround but I'm not sure what do you needs exactly. Commented Oct 6, 2016 at 15:00
  • 1
    This would be a better way than implementing Entity-specific code into a ChoiceType::class. Thanks, a good idea. I hoped that there is a simpler way and people having the same problem. Commented Oct 6, 2016 at 15:34
  • 1
    It's a bit hacky, but you could use FormEvents or a Data transformer to glom on to the field during submission or rendering of the field. Does that jibe with what you're hoping for? Commented Oct 6, 2016 at 20:42

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.