Symfony形成高记忆

时间:2015-05-22 20:16:34

标签: php forms symfony

我的Symfony应用程序中有一些复杂的表单,我用它通过FOSRestBundle处理我的API中的数据。

在某些情况下,我有10-15个嵌套的子表单,其中一些是具有20个左右记录的集合。

当我提交其中一种形式时,内存会增加几百mb,我无法解决原因。

以下是其中一个表单的示例 - 所有子表单都没有区别。

这里没有任何复杂的事情发生,所以我无法解决为什么几kb的JSON需要几百Mb来处理。

我哪里错了?

class SomeForm extends AbstractType
{

    /**
     * @param FormBuilderInterface $builder
     * @param array                $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {

        $builder
            ->add('field-a')
            ->add('field-b')
            ->add('field-c')
            ->add('field-d', 'collection', array(
                'type' => new AnotherForm1(),
                'allow_add' => true
                )
            )
            ->add('field-d', 'collection', array(
                'type' => new AnotherForm2(),
                'allow_add' => true
            ));
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
                                   'data_class'      => 'My\Bundle\Entity\SomeEntity',
                                   'csrf_protection' => false,
                               ));
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'SomeForm';
    }

}

2 个答案:

答案 0 :(得分:0)

这只是在开发环境或生产环境中吗?如果它是dev,它可能是导致大量内存峰值的记录器,因为它记录了集合中所有对象的加载操作。

答案 1 :(得分:0)

好的,所以我想我会留下这个以防万一它可以帮助任何人。让我们假设我有以下结构:

+ Dealer
  - Name [String]
  + Car [Collection]
    - NumberOfSeats [Int]
    + Manufacturer[Entity] 
      - Name
      - Phone
      - ..etc..

我遇到的问题是,如果我按照说明正确定义我的表格,我最终会得到这样的结果:

<强> DealerType

   /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class)
            ->add('cars', CollectionType::class, array(
                'entry_type' => CarType::class,
                'allow_add' => true,
                'allow_delete' => true,
                'by_reference' => false,

            ))
        ;
    }

<强> CarType

   /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('numberOfSeats', TextType::class)
            ->add('manufacturer', ManufacturerType::class)
        ;
    }

<强> ManufacturerType

/**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class)
            ->add('phone', TextType::class)            
        ;
    }

3种非常简单的形式。但是,假设您希望在处理POST的操作中执行以下操作:

    $data = json_decode($request->getContent(), true);
    $car = new Car();
    /** @var FormInterface $form */
    $form = $this->createForm(CarType::class, $car);
    $form->submit($data);

我们假设您发送的内容如下:

{ "name": "DealerA", "cars": [{"numberOfSeats": 2, "manufacturer": {"id": 1, "name": "ExistingManufacturer", "phone": "1234567"}}]}

如果你想传递一个现有的制造商(因此在POST请求中的id),我认为Symfony2只会抓住ID并找出制造商已经存在并且它将匹配关系bla bla bla。但事实并非如此。我经历过一堆&#34;内存允许超出&#34;错误,我仍然不知道这方面的关系,我认为如果你甚至没有创建父实体,如果你试图提供子实体的ID,那么SF2可能会发现自己处于某种循环中。

简而言之:如果它是一个已经存在的子实体并且你正在创建(POST)而不是更新(PUT)SF2会因内存消耗而导致服务器停机

解决方案:数据转换器

我的想法是,如果我试图在数据库中获得的是一个现有的实体,我并没有真正需要通过整个子实体,我只需要传递某种参考形式,以便它处理关系。这就是我想到数据变换器的地方,如果我只传递制造商的名字(而不是&#34;制造商&#34;:{&#34;名称&#34;:&#34; ..&#34; ,&#34;电话&#34;:&#34; ...&#34;}我应该只做&#34;制造商&#34;:&#34; ..&#34;)。唯一的缺点是#34;是通过POST / PUT,我无法修改该子实体的内容。但同样,如果实体是ManyToOne(许多汽车可以拥有相同的制造商),我在创建/更新汽车时没有看到修改制造商的重点。为此,我们通常会创建一个单独的制造商部分。

如果关系是OneToOne或OneToMany,则不需要此修复。

<强> ManufacturerNameToIdTransformer

基本上在Symfony2 Data Transformer Documentation之后,您可以获得一个简单的数据转换器,将制造商名称转换为制造商实体,反之亦然,然后您可以将CarType更改为以下内容:

    /**
 * @param FormBuilderInterface $builder
 * @param array $options
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('numberOfSeats', TextType::class)
        ->add('manufacturer', TextType::class)            
    ;

    $builder->get('manufacturer')->addModelTransformer(new ManufacturerNameToId($this->em));
}

正如您所看到的,我现在为制造商使用TextType,现在REST请求看起来像这样:

{ "name": "DealerA", "cars": [{"numberOfSeats": 2, "manufacturer": "ExistingManufacturerName"}]}

Voila!,现在它对ManyToOne关系更有意义,SF2在创建新实体时不会挂起。

希望它有所帮助,因为我浪费了半天的时间。