Highly Opinionated Thoughts on Programming

by Elnur Abdurrakhimov


How to Add or Update Several Model Objects at Once With a Single Form in Symfony

Mar 13, 2014


Most of the time you have a single form type per model class — for example, UserType form for the User model. It’s all simple and sweet and there are no problems with that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function registerAction(Request $request)
{
    $user = new User();
    $form = $this->formFactory->create('user', $user);
    $form->handleRequest($request);
    
    if ($form->isValid()) {
        $this->userManager->save($user);
        
        // ...
    }
    
    return [
        'form' => $form->createView(),
    ];
}

But sometimes you need to update multiple models in a single action. That’s where a lot of people get stuck. The question is: how do I bind a single form type to more than one model classes?

Meet the form model

A form model is a class holding instances of the models you need to update in a single action.

Let’s say when a user registers, you need to create two models at the same time: a User and an Account. You have a form type for each:

1
2
3
4
5
6
7
8
9
10
11
12
class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('email', 'email');
    }
    
    public function getName()
    {
        return 'user';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
class AccountType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('companyName', 'text');
    }
    
    public function getName()
    {
        return 'account';
    }
}

And a composite type that uses them both:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class RegistrationType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('user', 'user')
            ->add('account', 'account')
            ->add('submit', 'submit')
        ;
    }
        
    public function getName()
    {
        return 'registration';
    }
}

Since you can bind a form type to a single model object, here’s where the form model comes into play:

1
2
3
4
5
6
7
8
9
10
11
class RegistrationModel
{
    public $user;
    public $account;
    
    public function __construct(User $user, Account $account)
    {
        $this->user = $user;
        $this->account = $account;
    }
}

And here’s an action that ties everything together:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public function registerAction(Request $request)
{
    $registration = new RegistrationModel(new User(), new Account());
    $form = $this->formFactory->create('registration', $registration);
    $form->handleRequest($request);
    
    if ($form->isValid()) {
        $this->userManager->save($registration->user);
        $this->accountManager->save($registration->account);
        
        // ...
    }
    
    return [
        'form' => $form->createView(),
    ];
}


© Elnur Abdurrakhimov