symfony 2.3表单getData在子表单集合中不起作用

时间:2022-04-14 06:44:32

I have a form which contains a collection. So I have:

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

/* 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')
}

But I want to access the data inside the photo, so I tried inside the PhotoType:

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

$data = $builder->getData();

But it seems that it doesn't work, even if I am editting the form, so the photo collection has data. Why can't I access to the $builder->getData() in a form called by another?? Because I'm trying not to do and eventListener...

但似乎它不起作用,即使我在编辑表单,所以照片收集有数据。为什么我不能以另一个调用的形式访问$builder->getData() ?因为我试着不去做和eventListener…

4 个解决方案

#1


21  

To understand what is happening here you have to understand data mapping first. When you call

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

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

the form's data mapper is responsible for taking the given array (or object) and writing the nested values into the fields of the form, i.e. calling

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

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

But in your example, you are not dealing with Form, but with FormBuilder objects. FormBuilder is responsible for collecting the configuration of a form and using this information to produce a Form instance. As such, FormBuilder also lets you store the default data for the form. But since it's a simple configuration object only, it will not invoke the data mapper as of yet. For example:

但是在您的示例中,您不是在处理表单,而是在处理FormBuilder对象。FormBuilder负责收集表单的配置,并使用这些信息生成表单实例。因此,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());

This example will output:

这个例子将输出:

null
null

because data mapping takes place later, when we turn the FormBuilder into a Form instance. We can use this fact to set separate default values for the individual fields:

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

$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());

And the output:

和输出:

null
100    

Data locking is required to prevent the data mapper from overriding the default data you just stored. This is done automatically if you pass the "data" option.

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

At last, you will build the form. Now, FormBuilder calls Form::setData() where necessary, which in turn will invoke the data mapper:

最后,您将构建表单。现在,FormBuilder调用表单::setData(),在必要时,它将调用data mapper:

$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);

#2


3  

As Bernhard indicated, listeners are the only way to do this because the data is not available in the sub form yet. I used the eventListener to solve a similar requirement. Below is a simplified version of my code that I hope will be helpful:

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

I have a parent form for my View entity which has a lot of fields, as well as collection of other forms. One of the sub forms is for an associated entity ViewVersion, which actually needs to load another form collection for a dynamic entity that is the content type associated with the View. This content type could by one of many different types of entities, e.g Article, Profile, etc. So I need to find out what contentType is set in the View data and then find the dynamic path to that bundle, and include that formType.

我的视图实体有一个父窗体,它有许多字段,以及其他表单的集合。其中一个子表单用于关联的实体视图版本,它实际上需要为与视图关联的内容类型的动态实体加载另一个表单集合。这种内容类型可以是许多不同类型的实体之一,即e。g Article, Profile等等,所以我需要找出在视图数据中设置了什么内容类型,然后找到到那个bundle的动态路径,并包含那个formType。

Once you know how to do it, it's actually easy!

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

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

    }
}

That's it. I'm new to Symfony, so the idea of doing everything in an EventListener is foreign to me (and seems unnecessarily complex). But I hope once I understand the framework better, it will seem more intuitive. As this example indicates, it's not that complicated to do it with an Event Listener, you just wrap your code in that closure (or put it in it's own separate function as described in the docs).

就是这样。我对Symfony不熟悉,所以在EventListener中做任何事情的想法对我来说都是陌生的(而且似乎不必要地复杂)。但是我希望一旦我更好地理解了这个框架,它将看起来更直观。正如这个示例所示,使用事件监听器来完成它并不复杂,您只需将代码封装在该闭包中(或者将其放入文档中描述的独立函数中)。

I hope that helps someone!

我希望这能帮助某人!

#3


0  

In my case did not need the data necessarily when building the form, but when building the view (later). Just next to the buildForm function of my subform type class, I added the buildView function:

在我的例子中,在构建表单时并不需要数据,但是在构建视图时(稍后)。就在我的子表单类型类的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;
    }

    // ...
}

Because buildView is called later, the data is available there. In this example I used it to change the label of the form row of each item in the collection. Check out the list of possible vars.

因为稍后将调用buildView,所以数据在那里是可用的。在这个示例中,我使用它来更改集合中每个项目的表单行的标签。查看可能的vars列表。

#4


-1  

In a submit or if you are editing, you can access the data when you turn the FormBuilder into a Form instance. And for a collection type you can try this:

在提交或正在编辑时,可以在将FormBuilder转换为表单实例时访问数据。对于集合类型,你可以尝试以下方法:

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

#1


21  

To understand what is happening here you have to understand data mapping first. When you call

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

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

the form's data mapper is responsible for taking the given array (or object) and writing the nested values into the fields of the form, i.e. calling

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

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

But in your example, you are not dealing with Form, but with FormBuilder objects. FormBuilder is responsible for collecting the configuration of a form and using this information to produce a Form instance. As such, FormBuilder also lets you store the default data for the form. But since it's a simple configuration object only, it will not invoke the data mapper as of yet. For example:

但是在您的示例中,您不是在处理表单,而是在处理FormBuilder对象。FormBuilder负责收集表单的配置,并使用这些信息生成表单实例。因此,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());

This example will output:

这个例子将输出:

null
null

because data mapping takes place later, when we turn the FormBuilder into a Form instance. We can use this fact to set separate default values for the individual fields:

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

$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());

And the output:

和输出:

null
100    

Data locking is required to prevent the data mapper from overriding the default data you just stored. This is done automatically if you pass the "data" option.

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

At last, you will build the form. Now, FormBuilder calls Form::setData() where necessary, which in turn will invoke the data mapper:

最后,您将构建表单。现在,FormBuilder调用表单::setData(),在必要时,它将调用data mapper:

$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);

#2


3  

As Bernhard indicated, listeners are the only way to do this because the data is not available in the sub form yet. I used the eventListener to solve a similar requirement. Below is a simplified version of my code that I hope will be helpful:

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

I have a parent form for my View entity which has a lot of fields, as well as collection of other forms. One of the sub forms is for an associated entity ViewVersion, which actually needs to load another form collection for a dynamic entity that is the content type associated with the View. This content type could by one of many different types of entities, e.g Article, Profile, etc. So I need to find out what contentType is set in the View data and then find the dynamic path to that bundle, and include that formType.

我的视图实体有一个父窗体,它有许多字段,以及其他表单的集合。其中一个子表单用于关联的实体视图版本,它实际上需要为与视图关联的内容类型的动态实体加载另一个表单集合。这种内容类型可以是许多不同类型的实体之一,即e。g Article, Profile等等,所以我需要找出在视图数据中设置了什么内容类型,然后找到到那个bundle的动态路径,并包含那个formType。

Once you know how to do it, it's actually easy!

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

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

    }
}

That's it. I'm new to Symfony, so the idea of doing everything in an EventListener is foreign to me (and seems unnecessarily complex). But I hope once I understand the framework better, it will seem more intuitive. As this example indicates, it's not that complicated to do it with an Event Listener, you just wrap your code in that closure (or put it in it's own separate function as described in the docs).

就是这样。我对Symfony不熟悉,所以在EventListener中做任何事情的想法对我来说都是陌生的(而且似乎不必要地复杂)。但是我希望一旦我更好地理解了这个框架,它将看起来更直观。正如这个示例所示,使用事件监听器来完成它并不复杂,您只需将代码封装在该闭包中(或者将其放入文档中描述的独立函数中)。

I hope that helps someone!

我希望这能帮助某人!

#3


0  

In my case did not need the data necessarily when building the form, but when building the view (later). Just next to the buildForm function of my subform type class, I added the buildView function:

在我的例子中,在构建表单时并不需要数据,但是在构建视图时(稍后)。就在我的子表单类型类的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;
    }

    // ...
}

Because buildView is called later, the data is available there. In this example I used it to change the label of the form row of each item in the collection. Check out the list of possible vars.

因为稍后将调用buildView,所以数据在那里是可用的。在这个示例中,我使用它来更改集合中每个项目的表单行的标签。查看可能的vars列表。

#4


-1  

In a submit or if you are editing, you can access the data when you turn the FormBuilder into a Form instance. And for a collection type you can try this:

在提交或正在编辑时,可以在将FormBuilder转换为表单实例时访问数据。对于集合类型,你可以尝试以下方法:

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