Как добавить дополнительные параметры в Symfony EntityType form widget?

#forms #symfony

#формы #symfony

Вопрос:

У меня есть форма фильтра в Symfony, которая может фильтровать сущность. Для этой цели у меня есть поле с EntityFilterType функцией ( LexikBundleFormFilterBundleFilterFormTypeEntityFilterType ), которое просто расширяет встроенную функцию Symfony EntityType .

Теперь я хочу добавить к этому опции «все» и «нет» EntityType . Если бы это было a ChoiceType , я бы просто изменил choices массив, но EntityType он принимает только действительные идентификаторы объектов в качестве своего значения при отправке, а также только объекты в массиве, заданные параметру «выбор».

Мой вопрос: как я могу добавить дополнительные параметры в EntityType поле формы? Помимо уродливого способа переопределения сущности в ChoiceType поле? Есть идеи по этому поводу? Мне не хватает документированного способа?

Приветствую, spackmat

Комментарии:

1. Вам нужно что-то еще, что query_builder позволяет фильтровать объекты?

2. query_builder Определяет запросы, которые ограничивают выбор объектов, перечисленных в виджете. Кроме того, мне нужно еще несколько параметров / вариантов, которые не являются сущностями, а другими значениями, которые может использовать контроллер (или, в моем случае, логика фильтра).

3. Ну, я думаю об EntityType::class обходном пути расширения формы, но я не уверен, что именно вам нужно.

4. Это было бы лучшим способом, чем внедрение специфичного для объекта кода в ChoiceType::class . Спасибо, хорошая идея. Я надеялся, что есть более простой способ и люди, имеющие ту же проблему.

5. Это немного сложно, но вы можете использовать FormEvents или преобразователь данных для перехода к полю во время отправки или рендеринга поля. Соответствует ли это тому, на что вы надеетесь?

Ответ №1:

Прошло несколько лет, и настройка снова появляется:

У меня есть объект, связанный с ManyToMany, который я хочу отфильтровать для использования LexikFormFilterBundle. Но я также хочу разрешить явную фильтрацию для объектов, у которых нет такого связанного объекта. В моем случае я хочу разрешить фильтровать задачи, которые назначены некоторым конкретным пользователям, но также разрешить фильтровать задачи, которые вообще не назначены. Итак, начинаются проблемы.

Смотрите также эту проблему в LexikFormFilterBundle

Мое решение на данный момент, действительно, переключается на a ChoiceType::class и выглядит так:

 // We are in a buildForm function of a Filter-Form extending
// SymfonyComponentFormAbstractType 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],
            );
        },
    ])
;
  

И это работает: я могу найти задачи, назначенные некоторым пользователям и / или никому не назначенные. И когда я не заполняю поле, фильтр ничего не делает.