Integrate Symfony form with Vuejs
Tags: vue, symfony, form, ajax
Published: 2020-03-22 03:55:41

I think I'm old style because I prefer to separate html from javascript. I don't like how Angular (the new versions) and React force you to mingle html and javascript. Separating html, css, js is just something I've got used to and it's difficult to change that way of thinking. 

In any case, I'm working on a huge Symfony (4) based application, and most of my forms were built on Symfony form and twigs. The javascript library I used was Angularjs 1.x and it allowed me to support the "legacy" codebase just fine. I could easily embed Angularjs directive right into the html code by adding attributes/classes.

We are stuck with Angularjs 1.x however, the later version of Angular requires significant changes in the codebase that is impractical for us. That is until we found out about Vuejs. With Vuejs we could still keep our legacy codebase and it was possible to migrate from Angularjs 1.x to Vuejs 2.x without any difficulty except for 1 thing: form.

In Symfony, the form is a very powerful component and can be generated dynamically so it's difficult to know the exact structure of the form (which fields, nested structure, etc). With Vuejs if you want to use v-model you will have to ensure that data is already available. 

Let's use an example here (open the console log on codepen to see the error):

{% codepen https://codepen.io/raineng/pen/dyoqppE %}

You can see that if I have a v-model referring to undefined data I will get an error with Vuejs (in Angularjs it will automatically be set for you).

Many current tutorials on the internet recommend hard-coding the form data structure into your components. This is not good for us because:

1. Symfony form can have deeply nested structure.
2. There is no way that we know the form structure beforehand (form has events and transfomers that can take options and modify form's structure).
3. Even if we do know the form structure beforehand, we will have to create many components for all the forms that we have. This is not practical.

The solution I came up with, was to pre-populate a custom form component with the form data structure. I broke this into 2 phases:

###I. Phase 1: 
For fast migration, in phase 1 I want to keep all the current twig rendering of the form. For this purpose, I figured out that I could dump the form view (the view data object returned by Symfony) to a prop of the form component (I called it initialData). Using that initialData I populated the data of the form component to make sure that all the fields are pre-populated (meaning that all v-model are referring to valid data).

**Note 1:** Do note that Symfony form view object is huge, you don't want to dump every single thing in there, just enough for you to build the necessary data structure.

**Note 2:** Handling prototype was a pain in the a**, but it was possible to do. Prototype in Symfony form refers to the dynamic fields that can be added/removed on the fly. I used Vuejs dynamic components to get around it. Whenever the user clicks on the add button, the form wrapper component automatically adds a child component dynamically that can be rendered by Vuejs (using `<component :is="something">`).

**Note 3:** Be careful with Vuejs dynamic components. I had to spend days to debug why part of my form was re-rendered every time I make any change to the form data. It turned out that I was missing keep-alive and I was not "caching" the dynamic components properly.

I don't include any code sample for this here because the dump code that I'm using is rather hacky and only optimized for our app. If you want to see it please feel free to let me know.

###II. Phase 2:
In phase 2, I wanted to render the whole Symfony form using Vuejs (so no more twig). The reasons are:

1. It's faster (Symfony form prototype can generate huge amount of code)
2. It's easier to re-render part of the form
3. It just feels better

For this to work, I had to write a helper method in Symfony controller to dump the form view object via ajax request. This was quite simple but also a bit hacky. The form view object contains a huge amount of information, I had to cherry-pick only the necessary information I need.

I also wrote some helper method inside my form component to replicate the functionality of Symfony's form_row, form_widget, ... twig helper methods. These helper methods loop through the block_prefixes (if you are Symfony dev you know what I'm talking about) to pick the exact template to use. These templates are stored as script snippets with the corresponding ids. I believe I could create separate components for each form(input) type but for now, this feels more natural to the team. I could copy whatever code we have on twig and with some minor tweaks turn it into Vuejs compatible code. 

I hope this helps someone struggling out there. I know I should share some code but at the moment the code is still unstable, messy and ugly and I feel shameful to share it. If you want though, please do let me know.

Also, if there is a better way to integrate with Symfony form, please do share with me as well. I'm very new to Vuejs.