【问题标题】:Use query_builder on CollectionType in symfony4 forms?在 symfony4 表单中对 CollectionType 使用 query_builder?
【发布时间】:2019-05-19 13:36:55
【问题描述】:

在 symfony 4 表单中,我需要使用类似 query_builder 的选项,该选项在 EntityType 上可用,但来自 CollectionType。有一个类似的问题here 没有很好的答案。

在我的项目中,每个Site 实体都有很多Goal。每个Goal 都有一个数字目标和一个特定日期。我只想编辑特定日期的网站目标。问题是 CollectionType 表单会拉出 all 目标以显示在表单中,但我只想拉出给定日期的目标。如何?在CollectionType 上没有query_builder,就像在EntityType 上一样。我可以更改 Site 实体中的 getter,但我不知道如何将所需的日期传递给我的 getter。

目前我的解决方法是渲染整个表单(包含给定站点的所有相关目标),然后使用一些 javascript 隐藏所有目标,除了那些带有编辑日期的目标。这行得通,但对于在多个日期范围内有很多目标的网站来说,这是一个糟糕的解决方案。

我的Site 实体(仅显示相关代码):

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;
    }
}

和我相关的Goal实体:

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

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

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

    // ...
}

我的表格:

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
        ]);
    }
}

以及个人目标形式:

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,
        ]);
    }
}

【问题讨论】:

  • 我相信CollectionTypeallow_add and allow_delete 选项暗示提交的表单数据属于集合中的所有实体。在特定日期范围内仅编辑 Goals 可能最好在 CollectionType 之外进行。
  • 我可能完全错了,实际上!如果编辑(不是删除或添加新的)是路由中这些Goals 的唯一关注点,那么最好使用form events 完成基于日期范围呈现的过滤!
  • 谢谢@CameronHurd。你是对的,我不需要在这个应用程序中添加/删除目标,我只需要编辑现有的目标。表单事件的想法很有趣,但很难看到如何应用于整个实体数组和相关参数。

标签: symfony symfony4


【解决方案1】:

使用Form Events,同时避免CollectionType 表单的allow_addallow_delete 选项可能会让您找到正确的邻域:

首先 - 为便于举例,假设我们按年份过滤,并且年份是从 ?y=2018 样式的查询字符串中提取的。我们会将这些信息传递给表单生成器:

<?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]);
    // ...
}

这意味着我们应该更新SiteGoalsAdminForm 类的默认选项:

<?php

// SiteGoalsAdminForm.php

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

然后,在同一个类的buildForm 方法中,我们可以访问Site 对象并从中删除Goals,其中goalDate 的年份不属于表单的

<?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,
                ]);
            }
        );
    }

    // ...
}

不完全是 query_builder,但功能相似。

【讨论】:

  • 哇,这是严重的 symfony 形式的巫毒!非常感谢,它工作得很好。
【解决方案2】:

使用控制器中要在集合类型上设置的实体管理器过滤结果。

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

然后配置SiteGoalsType::class 以接受新的选项目标。

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

SiteGoalsType::classbuildForm方法中,从选项中将数据设置为集合类型字段。

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

确保将'mapped' =&gt; false 添加到您的集合类型字段,否则可能会导致删除不属于我们在控制器中编写的过滤器的记录。

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

【讨论】:

    猜你喜欢
    • 2016-10-09
    • 1970-01-01
    • 1970-01-01
    • 2018-09-21
    • 1970-01-01
    • 1970-01-01
    • 2017-02-01
    • 2017-01-15
    • 1970-01-01
    相关资源
    最近更新 更多