Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony 2.5 : Entity Type, prevent fetching all records when form submitted

I have a form containing some classics fields, and a drop-down list of Users Entities (handled with AngularJs, not with a classic form view). When I submit the form, the whole object (and the User selected via the drop down) is sent to my controller with an Ajax Request.

class MyRestController{

    public function MyRestAction(...){
        ...
        $myObject = new MyObject();

        $myForm = $this->createForm(new MyFormType(), $myObject);

        $myForm->handleRequest($request);

        ...
    }
}

class MyFormType extends AbstractType{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add(...),
            ->add(...),
            ->add(...),
            ->add('user', 'entity', array(
                'required' => true,
                'class' => 'User',
                'property' => 'id',
            ))
        ;
            ...
    }

}

Then I persist the whole object and flush it. It works perfectly. But the process is insanely slow...

The problem comes from the handleRequest function, when the getChoicesForValue function (EntityChoiceList.php) is called. In my case, the process do a findAll() request on the User entity, fetching all records (thousands) from the database.

In this function, there is a condition checking if the EntityChoiceList has a entityLoader like that :

if ($this->idAsValue && $this->entityLoader) {
    // do a findById()
} else {
    // do a findAll()
}

( I really simplified the code, which is more complex, but you can find the whole code in the EntityChoiceList.php file). And after debugging my app, I could see that $this->idAsValue was true, and $this->entityLoader was null, explaining why it does a findAll().

So I wonder if there is an option or anything to make this process faster and prevent fetching all records ?

Thanks.

EDIT :

I think I could try to replace the handleRequest and hydrate objects myself, but I hope there is another way keeping the classic process.

like image 233
Bapt Avatar asked Nov 08 '22 10:11

Bapt


1 Answers

Finally, I found a solution. I replaced my entity type by a simple text type, and next created my own DataTransformer class in order to transform the user string id to a real User object. In this way the user object is correctly hydrated without fetching thousands records, significantly improving the loading speed.

Here is exactly what I have done :

I changed :

class MyFormType extends AbstractType{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('user', 'entity', array(
                'required' => true,
                'class' => 'User',
                'property' => 'id',
            ))
        ;
    }

}

to :

class MyFormType extends AbstractType{

    private $manager;

    public function __construct(ObjectManager $manager)
    {
        $this->manager = $manager;
    }
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('user', 'text', array(
                'required' => true,
            ))
        ;
       // we use a specific ModelTransformer (UserTransformer) created by ourself (see below)
       $builder->get('user')->addModelTransformer(new UserTransformer($this->manager));
    }

}

And I created my own UserTransformer class like this one :

class UserTransformer implements DataTransformerInterface
{
    private $manager;

    public function __construct(ObjectManager $manager)
    {
        $this->manager = $manager;
    }

    public function transform($user)
    {
        if (null === $user) {
            return '';
        }

        return $user->getId();
    }

    public function reverseTransform($id)
    {
        if (!$id) {
            return null;
        }

        $user= $this->manager
            ->getRepository(User::class)
            ->find($id)
        ;

        if (null === $user) {
            throw new TransformationFailedException(sprintf(
                'There is no User with the id "%s" !',
                $id
            ));
        }
        return $user;
    }
}

And finally I pass the EntityManager to the form like that :

class MyRestController{

    public function MyRestAction(...){
        ...
        $myObject = new MyObject();

        $manager = $this->getDoctrine()->getManager();
        $myForm = $this->createForm(new MyFormType($manager), $myObject);

        $myForm->handleRequest($request);

        ...
    }
}

I found that solution in the Symfony doc here : https://symfony.com/doc/2.7/form/data_transformers.html

It works perfectly and now my App is 10x faster !

like image 138
Bapt Avatar answered Nov 15 '22 07:11

Bapt