spackmat
spackmat

Reputation: 995

How to add additional options on Symfony EntityType form widget?

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

Upvotes: 3

Views: 957

Answers (1)

spackmat
spackmat

Reputation: 995

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.

Upvotes: 1

Related Questions