Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How-to: Optimize Symfony's forms' performance?

I have a form that is the bottleneck of my ajax-request.

    $order = $this->getDoctrine()
        ->getRepository('AcmeMyBundle:Order')
        ->find($id);
    $order = $order ? $order : new Order();

    $form = $this->createForm(new OrderType(), $order);

    $formView = $form->createView();

    return $this->render(
        'AcmeMyBundle:Ajax:order_edit.html.twig',
        array(
            'form' => $formView,
        )
    );

For more cleaner code I deleted stopwatch statements.

My OrderType has next fields:

    $builder
        ->add('status') // enum (string)
        ->add('paid_status') // enum (string)
        ->add('purchases_price') // int
        ->add('discount_price') // int
        ->add('delivery_price') // int
        ->add('delivery_real_price', null, array('required' => false)) // int
        ->add('buyer_name') // string
        ->add('buyer_phone') // string
        ->add('buyer_email') // string
        ->add('buyer_address') // string
        ->add('comment') // string
        ->add('manager_comment') // string
        ->add('delivery_type') // enum (string)
        ->add('delivery_track_id') // string
        ->add('payment_method') // enum (string)
        ->add('payment_id') // string
        ->add('reward') // int
        ->add('reward_status') // enum (string)
        ->add('container') // string
        ->add('partner') // Entity: User
        ->add('website', 'website') // Entity: Website
        ->add('products', 'collection', array( // Entity: Purchase
            'type' => 'purchase',
            'allow_add' => true,
            'allow_delete' => true,
            'by_reference' => false,
            'property_path' => 'purchases',
            'error_bubbling' => false,
        ));

Purchase type:

    $builder
        ->add('amount')
        ->add('price')
        ->add('code', 'variant', array(
            'property_path' => 'variantEntity',
            'data_class' => '\Acme\MyBundle\Entity\Simpla\Variant'
        ))
    ;

Also Purchase type has a listener that is not significant here. It is represented in Symfony profiler below as variant_retrieve, purchase_form_creating. You can see that it takes about 200ms.

Here I put the result of profilers: SymfonyProfilerBlackFire ProfilerBlackFire Profiler

As you can see: $this->createForm(...) takes 1011ms, $form->createView(); takes 2876ms and form rendering in twig is also very slow: 4335ms. As stated by blackfire profiler all the deal in ObjectHydrator::gatherRowData() and UnitOfWork::createEntity().

Method createEntity() called 2223 times because there is some field that mapped with Variant entity and has form type Entity. But as you can see from above code there is no entity types for variant. My VariantType is simple extended text form type that has modelTransformer. To not mess up everything you can see code for similar Type class at docs.

I found with XDebug that buildView for VariantType has been called in Purchase's buildView with text form type. But after that from somewhere buildView for VariantType was called again and in this case it has entity form type. How can it be possible? I tried to define empty array in choices and preferred_choices on every my form type but it didn't change anything. What I need to do to prevent EntityChoiceList to be loaded for my form?

like image 929
Michael Sivolobov Avatar asked Nov 06 '14 11:11

Michael Sivolobov


1 Answers

The described behavior looks as the work of the guesser. I have the feeling that there is need to show an some additional code (listeners, VariantType, WebsiteType, PartnerType).

Let's assume a some class has association variant to Variant and FormType for this class has code ->add('variant') without explicit specifying type (as I see there is a lot of places where the type is not specified). Then DoctrineOrmTypeGuesser comes in the game.

https://github.com/symfony/symfony/blob/2.7/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php#L46

This code assign the entity type (!) to this child. The EntityRepository::findAll() is called and all variants from DB are hydrated.

As for another form optimization ways:

  • Try to specify type in all possible cases to prevent a type guessing;
  • Use SELECT with JOINs to get an order as new sub-requests to DB are sent to set an underlying data for an every form maps relation;
  • Preserve keys for collection elements on a submission as a removing of a single element without a keys preserving will trigger unnecessary updates.
like image 142
origaminal Avatar answered Sep 28 '22 11:09

origaminal