loading...

Integrate Symfony form with Vuejs

yellow1912 profile image yellow1912 ・4 min read

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):

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.

Posted on by:

Discussion

markdown guide
 

Interesting... Phase 1 seems to be the correct approach to migrate the code base to vuejs without refactoring the entire code base at once.

Phase 2: I am not quite sure, I prefer to work with json and keep the front-end forms decoupled from back-end dependency and for larger projects I use API Platform with vuejs.

 

You are absolutely right regarding APIs for phase 2. At the moment we still have an internal debate on how this should be handled because the main frontend code we have right now is the backend control. It's so much easier to dynamically configure the form on the server code (Symfony) and with Validation and ORM components the post-processing is taken care of for us in most cases. In the future though, API does seem like the way to go and eventually we will go that way.

How do you handle complex data with APIs? Restful APIs seem to be overkill (and with heavy overhead for nested data structure), while graphql does have its own issues.

 

Yes, I agree that in certain scenarios working with Symfony forms a much easier and a huge time saver. However, the API approach has its benefits when we are using Vue on the front-end. There is no much difference in post-processing compared to the traditional form handling and validation can be simplified a lot with Vue front-end.

Complex data? I am not sure what you mean by the complex nested data structure.

Anyway, don't try anything new on an existing codebase. You can do some experiments with a small scale project to understand the architecture and challenges.

Note: API Platform is a much better choice for REST API implementation but it's a bit overkill to implement it in an existing codebase. FOSRestBundle is better for gradual migration.