php - How to keep selected value in Symfony EntityType if value was deleted by softdeletable? - Stack Overflow

admin2025-04-17  2

First of all, I use Gedmo's softdeletable to manage deleted entities. Also I have my own EntityField:

namespace App\Form\Fields;

use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;

class EntityField extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        parent::configureOptions($resolver);

        // some additional configuration

        $resolver->setDefaults([
            'query_builder' => function (EntityRepository $er): QueryBuilder {
                return $er->createQueryBuilder('entity')
                    ->andWhere('entity.deletedAt IS NULL');
            },
        ]);
    }

    public function getParent(): string
    {
        return EntityType::class;
    }
}

As you can see, I don't use softdeleteable filter in Doctrine, but instead I use custom query_builder in my EntityField.

It works fine as long as I've softdeleted entity thas has been already used.

E.g. I have User, File and FileType entities, User has collection of Files and File has FileType. I've softdeleted some FileType and for me it means I don't want to create new Files of this FileType, but all existing Files of this FileType are still legal and have to be properly displayed in forms. "Properly" means, in this case, that if I render File form and this exact file has deleted FileType, the select must have this FileType. But now I have no selected value in this field and it loks like "there is no selected file type for this file" that is not.

I tried to modify query_builder in FormEvents::PRE_SET_DATA event and add the value from the database, but it looks like list of choises is generated before this event, because I can see updated query_builder in the form, but the real query corresponds to unmodified query_buider.

There are buildView and finishView methods, but I don't think it's a good idea to use them, because in this case I need to manually modify form attributes.

So, is there a "Symfony way" to do that? Maybe I forgot some form options?

First of all, I use Gedmo's softdeletable to manage deleted entities. Also I have my own EntityField:

namespace App\Form\Fields;

use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;

class EntityField extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        parent::configureOptions($resolver);

        // some additional configuration

        $resolver->setDefaults([
            'query_builder' => function (EntityRepository $er): QueryBuilder {
                return $er->createQueryBuilder('entity')
                    ->andWhere('entity.deletedAt IS NULL');
            },
        ]);
    }

    public function getParent(): string
    {
        return EntityType::class;
    }
}

As you can see, I don't use softdeleteable filter in Doctrine, but instead I use custom query_builder in my EntityField.

It works fine as long as I've softdeleted entity thas has been already used.

E.g. I have User, File and FileType entities, User has collection of Files and File has FileType. I've softdeleted some FileType and for me it means I don't want to create new Files of this FileType, but all existing Files of this FileType are still legal and have to be properly displayed in forms. "Properly" means, in this case, that if I render File form and this exact file has deleted FileType, the select must have this FileType. But now I have no selected value in this field and it loks like "there is no selected file type for this file" that is not.

I tried to modify query_builder in FormEvents::PRE_SET_DATA event and add the value from the database, but it looks like list of choises is generated before this event, because I can see updated query_builder in the form, but the real query corresponds to unmodified query_buider.

There are buildView and finishView methods, but I don't think it's a good idea to use them, because in this case I need to manually modify form attributes.

So, is there a "Symfony way" to do that? Maybe I forgot some form options?

Share Improve this question asked Feb 1 at 6:37 Andrey YanduganovAndrey Yanduganov 739 bronze badges 3
  • Why are you not using Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter? – craigh Commented Feb 1 at 13:14
  • @craigh because in some cases I need all values, including softdeleted. Anyway, if I understand how this filter works, I'll get same result, because final choice list will not contain softdeleted entity – Andrey Yanduganov Commented Feb 1 at 14:40
  • @craigh actualy in most cases I need all values. And I've just tried SoftDeleteableFilter and it totally crashed entire application) – Andrey Yanduganov Commented Feb 1 at 15:26
Add a comment  | 

1 Answer 1

Reset to default 0

Well, I did it.
As I assumed from the very beginning, the only way to solve this problem is using buildView method:

public function buildView(FormView $view, FormInterface $form, array $options): void
{
    if ($options['show_selected_choice']) {
        $choices = $view->vars['choices'];
        if ($data = $view->vars['data']) {
            $selectedValue = $data->getId();
            if (!array_key_exists($selectedValue, $choices)) {
                $choiceView = new ChoiceView(
                    $data,
                    $this->getChoiceParam($data, $options['choice_value']),
                    $this->getChoiceParam($data, $options['choice_label']),
                    [
                        ...$choices[array_key_first($choices)]->attr,
                        'disabled' => true,
                    ]
                );
                $view->vars['choices'][$selectedValue] = $choiceView;
                if ($options['expanded']) {
                    $this->addSubForm($form, $selectedValue, $choiceView, $options);
                }
            }
        }
    }
}

private function getChoiceParam(object $entity, mixed $param): string
{
    return match (true) {
        $param instanceof AbstractStaticOption => $param->getOption()($entity),
        $param instanceof PropertyPath         => $this->propertyAccessor->getValue($entity, $param),
        is_callable($param)                    => $param($entity),
        is_string($param)                      => $this->propertyAccessor->getValue($entity, new PropertyPath($param)),
        default                                => (string)$param,
    };
}

Note the $this->addSubForm: you need to add child form for expanded fields like checkboxes or radio. And you can just copy-paste private function addSubForm from ChoiceType.

Ah, and don't forget to inject PropertyAccessorInterface $propertyAccessor to your form class.

转载请注明原文地址:http://anycun.com/QandA/1744836097a88292.html