【问题标题】:symfony 2.3 form getData doesn't work in subforms collectionssymfony 2.3 表单 getData 在子表单集合中不起作用
【发布时间】:2013-09-23 03:10:24
【问题描述】:

我有一个包含集合的表单。所以我有:

/* my type */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
    ->add('name')
    ->add('photos','collection',array(
        'type'=> new PhotoType(),
        'allow_add'=>true));
}

/*Photo Type*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
    ->add('photoname')
    ->add('size')
}

但是我想访问照片里面的数据,所以我在PhotoType里面试了一下:

$data = $builder->getData();

但是好像不行,即使我在编辑表格,所以照片集有数据。 为什么我不能以其他人调用的形式访问 $builder->getData() ?因为我试图不做和 eventListener...

【问题讨论】:

  • wherewhy 您尝试使用 getData() 访问数据?

标签: forms symfony collections builder


【解决方案1】:

要添加到 Chadwick Meyer,(在 Symfony 4 中,但可能适用于早期版本),需要一个事件侦听器来访问集合中的数据,因为很多时候数据尚未创建和/或尚未创建已关联或嵌入到集合中。但是,通过事件侦听器实际获取集合中的数据存在一些复杂性,这在日常使用中变得很重要。

在您的照片表单构建器中,您必须包含一个事件侦听器:

/*Photo Type*/
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
        ->add('photoname')
        ->add('size');

        $builder->addEventListener(FormEvents::POST_SET_DATA,
             function (FormEvent $event)  {

                $form = $event->getForm();

                // this would be your entity
                 $photo = $event->getData();

                //Do something with the photo data.
            }
        );

    }

但是...如果你想用它做点什么,你需要确保你测试空值,因为在数据实际动态创建之前和之后多次触发事件。例如,如果您想即时修改表单,例如添加某种提交按钮:

/*Photo Type*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
    ->add('photoname')
    ->add('size')


    $builder->addEventListener(FormEvents::POST_SET_DATA,
         function (FormEvent $event) use ($formModifier) {

                $form = $event->getForm();
                // this would be your entity
                 $photo = $event->getData();

                $formModifier($form,$photo);
         }
    );

    $formModifier = function (FormInterface $form, Photo $photo = null) {

        if (!empty($photo)){//Critical to do this test to avoid errors and get to events with data 
            $form->add('submitButton', SubmitType::class, array(
                 'label' => 'Do Something',
                ));
            }

    };
}

最后,请注意,在某些情况下,并非所有数据都会与特定实体相关联,直到它实际保存在数据库中。例如,如果实体是新创建的,它还没有它的 id,这通常是在坚持期间由学说或类似的自动生成的。因此,为了在持久化之前将提交按钮或类似实体与集合中的该实体相关联,您可能必须使“名称”字段唯一或为实体创建一个单独的字段以保存唯一类型参数和在持久化之前以独特的方式生成它,以便在表单创建期间将提交按钮之类的东西与实体相关联。

【讨论】:

    【解决方案2】:

    在我的情况下,在构建表单时不一定需要数据,但在构建视图时(稍后)。就在我的子表单类型类的 buildForm 函数旁边,我添加了 buildView 函数:

    namespace AppBundle\Form;
    
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\Form\FormView;
    use Symfony\Component\Form\FormInterface;
    
    class MyType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            // ...
        }
    
        public function buildView(FormView $view, FormInterface $form, array $options)
        {
            $data = $form->getData();
            $view->vars['name'] = $data->objproporwhatever;
        }
    
        // ...
    }
    

    因为 buildView 稍后被调用,所以数据在那里可用。在此示例中,我使用它来更改集合中每个项目的表单行的标签。查看the list of possible vars

    【讨论】:

      【解决方案3】:

      在提交或编辑时,您可以在将 FormBuilder 转换为 Form 实例时访问数据。对于集合类型,您可以尝试以下操作:

      ...
      $form = $formBuilder->getForm();
      ...
      if ($this->getRestMethod() == 'POST') {
          $form->handleRequest($this->get('request'));
          if ($form->isValid()) {
              $formData = $form->getData();
              foreach ($formData['photos'] as $key => $collectionRow) {
                  var_dump($collectionRow['photoname']);
                  var_dump($collectionRow['size']);
              }
          }
      }
      

      【讨论】:

        【解决方案4】:

        正如 Bernhard 所指出的,监听器是唯一的方法,因为数据在子表单中还不可用。我使用 eventListener 来解决类似的要求。以下是我的代码的简化版本,希望对您有所帮助:

        我的View 实体有一个父表单,它有很多字段以及其他表单的集合。其中一个子表单用于关联实体ViewVersion,它实际上需要为动态实体加载另一个表单集合,该动态实体是与View 关联的内容类型。此内容类型可以是许多不同类型的实体之一,例如文章、个人资料等。所以我需要找出在 View 数据中设置的 contentType,然后找到该捆绑包的动态路径,并包含该 formType .

        一旦你知道怎么做,其实很简单!

        class ViewType extends AbstractType
        {
            public function buildForm(FormBuilderInterface $builder, array $options)
            {
                $builder
                    // Basic Fields Here
                    // ...
                    // ->add('foo', 'text')
                    // ...
                    // Load a sub form type for an associated entity
                    ->add('version', new ViewVersionType())
                ;
            }
        }
        
        
        
        class ViewVersionType extends AbstractType
        {
            public function buildForm(FormBuilderInterface $builder, array $options)
            {
                $builder
                    // Basic Fields Here
                    // ...
                    // ->add('foo', 'text')
                    // ...
                ;
        
                // In order to load the correct associated entity's formType, 
                // I need to get the form data. But it doesn't exist yet.
                // So I need to use an Event Listener
                $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
                    // Get the current form
                    $form = $event->getForm();
                    // Get the data for this form (in this case it's the sub form's entity)
                    // not the main form's entity
                    $viewVersion = $event->getData();
                    // Since the variables I need are in the parent entity, I have to fetch that
                    $view = $viewVersion->getView();
                    // Add the associated sub formType for the Content Type specified by this view
                    // create a dynamic path to the formType
                    $contentPath = $view->namespace_bundle.'\\Form\\Type\\'.$view->getContentType()->getBundle().'Type';
                    // Add this as a sub form type
                    $form->add('content', new $contentPath, array(
                        'label' => false
                    ));
                });
        
            }
        }
        

        就是这样。我是 Symfony 的新手,所以在 EventListener 中做所有事情的想法对我来说是陌生的(而且似乎不必要地复杂)。但我希望一旦我更好地理解了这个框架,它看起来会更直观。正如这个例子所表明的,使用事件侦听器并没有那么复杂,您只需将代码包装在该闭包中(或将其放入它自己的单独函数中,如 described in the docs)。

        希望对大家有所帮助!

        【讨论】:

          【解决方案5】:

          要了解这里发生了什么,您必须首先了解数据映射。当你打电话时

          $form->setData(array('photoname' => 'Foobar', 'size' => 500));
          

          表单的数据映射器负责获取给定的数组(或对象)并将嵌套值写入表单的字段,即调用

          $form->get('photoname')->setData('Foobar');
          $form->get('size')->setData(500);
          

          但在您的示例中,您处理的不是Form,而是FormBuilder 对象。 FormBuilder 负责收集表单的配置并使用此信息生成Form 实例。因此,FormBuilder 还允许您存储表单的默认数据。但由于它只是一个简单的配置对象,它不会调用数据映射器。例如:

          $builder = $factory->createBuilder()
              ->add('photoname')
              ->add('size')
              ->setData(array('photoname' => 'Foobar', 'size' => 500));
          
          print_r($builder->get('photoname')->getData());
          print_r($builder->get('size')->getData());
          

          这个例子会输出:

          null
          null
          

          因为数据映射发生在稍后,当我们将FormBuilder 转换为Form 实例时。我们可以利用这一事实为各个字段设置单独的默认值:

          $builder->add('size', null, array('data' => 100));
          // which is equivalent to
          $builder->get('size')
              ->setData(100)
              ->setDataLocked(true);
          
          print_r($builder->get('photoname')->getData());
          print_r($builder->get('size')->getData());
          

          还有输出:

          null
          100    
          

          需要数据锁定以防止数据映射器覆盖您刚刚存储的默认数据。如果您传递“数据”选项,这将自动完成。

          最后,您将构建表单。现在,FormBuilder 会在必要时调用 Form::setData(),而后者又会调用数据映射器:

          $form = $builder->getForm();
          
          // internally, the following methods are called:
          
          // 1) because of the default data configured for the "size" field
          $form->get('size')->setData(100);
          
          // 2) because of the default data configured for the main form
          $form->setData(array('photoname' => 'Foobar', 'size' => 500));
          
          // 2a) as a result of data mapping
          $form->get('photoname')->setData('Foobar');
          
          // 2b) as a result of data mapping (but ignored, because the data was locked)
          $form->get('size')->setData(500);
          

          【讨论】:

          • 你只描述了什么不起作用,而不是你想要做什么:) 但我猜你想使用事件监听器:symfony.com/doc/current/cookbook/form/…
          • 我正在尝试在不使用事件侦听器的情况下执行事件侦听器所做的事情。我只想使用 $builder->getData() 来查看对象的一些变量,就像我在第一级表单(不是子表单)中所做的那样。有没有办法做到这一点?
          • 不,抱歉。这就是事件监听器的用途(我希望我能解释原因)。
          • 如果有人能解释为什么你可以在父 formBuilder 中访问 $builder->getData() ,但在关联实体的子 formType 中,你不能。我认为该子类型关联实体的变量对象至少应该是可访问的,如果不是整个表单数据的话。您如何访问此子表单类型中的数据?似乎我读到的关于 Symfony 中每个主题的每个解决方案都是创建一个监听器……即使是最简单的事情。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-02-27
          • 2017-12-20
          • 1970-01-01
          相关资源
          最近更新 更多