Use query_builder on CollectionType in symfony4 forms?

☆樱花仙子☆ 提交于 2021-02-08 03:31:22

问题


In a symfony 4 form, I need to use something like a query_builder option that is available on EntityType but from a CollectionType. There is a similar question here with no good answers.

In my project, each Site entity has many Goal. Each Goal has a numeric goal and a specific date. I'd like to edit the goals of a site for a specific date only. The problem is that a CollectionType form pulls all goals to show in the form, but I only want to pull the goals for a given date. How? There is no query_builder on a CollectionType like there is on an EntityType. I could change the getter in my Site entity, but I don't know how to pass the needed date to my getter.

For now my work-around is to render the entire form (with ALL associated goals for a given site), and then use some javascript to hide all goals except those with the date to edit. This works, but it's a terrible solution for sites with lots of goals spanning a range of dates.

My Site entity (only relevant code is shown):

class Site
{
    public function __construct()
    {
        $this->goals = new ArrayCollection();
    }

    /** @ORM\OneToMany(targetEntity="App\Entity\Goal", mappedBy="site") */
    private $goals;


    public function getGoals()
    {
        return $this->goals;
    }
}

and my related Goal entity:

class Goal
{
    /** @ORM\Column(type="date") */
    private $goalDate;

    /** @ORM\Column(type="integer") */
    private $goal;

    /** @ORM\ManyToOne(targetEntity="App\Entity\Site", inversedBy="goals") */
    private $site;

    // ...
}

My forms:

class SiteGoalsAdminForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('goals', CollectionType::class, [
                'entry_type' => GoalsEmbeddedForm::class,
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Site::class
        ]);
    }
}

and the individual goal form:

class GoalsEmbeddedForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('goal', IntegerType::class)
            ->add('goalDate', DateType::class);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Goal::class,
        ]);
    }
}

回答1:


Using Form Events, while avoiding the allow_add and allow_delete options for the CollectionType form might land you in the right neighbourhood:

First - let's assume we're filtering by year, for ease of example, and that the year is being scooped up from a ?y=2018 style of querystring. We'll pass that info down to the form builder:

<?php
// Inside a *Action method of a controller

public function index(Request $request): Response
{
    // ...
    $filteredYear = $request->get('y');
    $form         = $this->createForm(SiteGoalsAdminForm::class, $site, ['year_filter' => $filteredYear]);
    // ...
}

This implies we should be updating the default options for the SiteGoalsAdminForm class:

<?php

// SiteGoalsAdminForm.php

// ...
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
          'data_class' => Site::class,
          'year_filter' => 2018
        ]);
     }
// ...

Then, in the buildForm method of that same class, we could access the Site object and remove Goals from it where the year of the goalDate did not fall inside the form's

<?php

// SiteGoalsAdminForm.php

namespace App\Form;

// ... other `use` statements, plus:
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;

class SiteGoalsAdminForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($options) {
                $form = $event->getForm();
                /** @var Site */
                $site  = $event->getData();
                $goals = $site->getGoals();

                foreach ($goals as $g) {
                    if ($g->getGoalDate()->format('Y') !== (string) $options['year_filter']) {
                        $site->removeGoal($g);
                    }
                }

                $form->add('goals', CollectionType::class, [
                    'entry_type' => GoalsEmbeddedForm::class,
                ]);
            }
        );
    }

    // ...
}

Not a query_builder exactly, but functionally similar.




回答2:


Filter the results using the entity manager in the controller that you want to set on the collection type.

$goals = $entityManager->getRepository(Goals::class)->findBy(['year' => 2020]);
$form = $this->createForm(SiteGoalsType::class, $site, [
   'goals' => $goals
]);

Then configure the SiteGoalsType::class to accept new option goals.

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'data_class' => Site::class,
    ]);
    $resolver->setRequired(['goals']);
}

In the buildForm method of SiteGoalsType::class Set the data to the collection type field from the options.

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('goals', Type\CollectionType::class, [
        'entry_type' => GoalsEmbeddedType::class,
        'data' => $options['goals'],
        'mapped` => false
    ]);
}

Make sure the add the 'mapped' => false to your collection type field else it may lead to removing the records that didn't falls in the filter we have written in the controller.

$goals = $entityManager->getRepository(Goals::class)->findBy(['year' => 2020]);


来源:https://stackoverflow.com/questions/53844825/use-query-builder-on-collectiontype-in-symfony4-forms

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!