Hello fellow developers and blog-enthusiasts alike! Today I would like to present an overview, as to how one can go about building a Form-Builder feature in Symfony 4. This is part 1 of the Form-Builder series, this part will elaborate on the back-end aspect of the application. On the next installation, the front-end aspect shall be discussed. So stay tuned! So what is a “Form-Builder” you say? As the name suggests, its a application (or a feature) that enables you to build customizable forms according to the needs of the user, (which when submitted, will be converted to a Ticket inside the UVDesk system). For starters, we begin by defining our routes, which will be utilized in our twig file and our Symfony controllers. In my private.yaml file , I have the following routes defined: # FORMBUILDER PATHS: formbuilder_settings: path: /settings/formbuilder controller: Webkul\UVDesk\ExtensionFrameworkBundle\Controller\FormBuilderChannel::loadFormBuilders formbuilder_load_configurations_xhr: path: /settings/formbuilder/xhr controller: Webkul\UVDesk\ExtensionFrameworkBundle\Controller\FormBuilderChannelXHR::loadFormsXHR formbuilder_create_configuration: path: /settings/formbuilder/create controller: Webkul\UVDesk\ExtensionFrameworkBundle\Controller\FormBuilderChannel::createFormBuilderConfiguration formbuilder_update_configuration: path: /settings/formbuilder/update/{id} controller: Webkul\UVDesk\ExtensionFrameworkBundle\Controller\FormBuilderChannel::updateFormConfiguration defaults: { id: ''} formbuilder_remove_configuration_xhr: path: /settings/formbuilder/remove/{id} controller: Webkul\UVDesk\ExtensionFrameworkBundle\Controller\FormBuilderChannelXHR::removeFormConfiguration defaults: {id: ''} 12345678910111213141516171819202122 # FORMBUILDER PATHS:formbuilder_settings: path: /settings/formbuilder controller: Webkul\UVDesk\ExtensionFrameworkBundle\Controller\FormBuilderChannel::loadFormBuilders formbuilder_load_configurations_xhr: path: /settings/formbuilder/xhr controller: Webkul\UVDesk\ExtensionFrameworkBundle\Controller\FormBuilderChannelXHR::loadFormsXHR formbuilder_create_configuration: path: /settings/formbuilder/create controller: Webkul\UVDesk\ExtensionFrameworkBundle\Controller\FormBuilderChannel::createFormBuilderConfiguration formbuilder_update_configuration: path: /settings/formbuilder/update/{id} controller: Webkul\UVDesk\ExtensionFrameworkBundle\Controller\FormBuilderChannel::updateFormConfiguration defaults: { id: ''} formbuilder_remove_configuration_xhr: path: /settings/formbuilder/remove/{id} controller: Webkul\UVDesk\ExtensionFrameworkBundle\Controller\FormBuilderChannelXHR::removeFormConfiguration defaults: {id: ''} I also have a route defined in my public.yaml file. This is so that when we share a link of the form, the user can generate a ticket without being logged-in, (think Google-forms). The public route is as follows: formbuilder_preview_form: path: /formbuilder/preview/{id} controller: Webkul\UVDesk\ExtensionFrameworkBundle\Controller\FormBuilderChannel::previewForm defaults: {id: ''} 1234 formbuilder_preview_form: path: /formbuilder/preview/{id} controller: Webkul\UVDesk\ExtensionFrameworkBundle\Controller\FormBuilderChannel::previewForm defaults: {id: ''} To handle the back-end/ configuration aspect of this feature, I have decided to go with a simply JSON file, which comes in handy for easy CRUD (Create, Read, Update, Delete) functionalities. The data for the form-builder is saved in a JSON file. I use a binary-system to indicate what fields to render in the form; these fields are to be selected by the user, passed and processed by the Symfony controller, then saved inside the configuration file. The content of the file is presented below; according to your own specific requirements, yours will probably differ. [ { "id": "5a210b889ca61350e0f060f7bfec06f79713917d", "form_name": "first", "name": "1", "type": "1", "subject": "1", "gdpr": "1", "order_no": "1", "file": "1" }, { "id": "5cc50196e241faa3ee8507aee0973419d522008c", "form_name": "second", "name": "1", "type": "1", "subject": "1", "gdpr": "1", "order_no": "1", "file": "1" } ] 12345678910111213141516171819202122 [ { "id": "5a210b889ca61350e0f060f7bfec06f79713917d", "form_name": "first", "name": "1", "type": "1", "subject": "1", "gdpr": "1", "order_no": "1", "file": "1" }, { "id": "5cc50196e241faa3ee8507aee0973419d522008c", "form_name": "second", "name": "1", "type": "1", "subject": "1", "gdpr": "1", "order_no": "1", "file": "1" }] Our discussion about the back-end would not be complete if we did not discuss about the almighty Controller! I have two controller files, FormBuilderChannel.php and FormBuilderChannelXHR.php. The code for FormBuidlerChannelXHR is as followed: class FormBuilderChannelXHR extends Controller { const CONFIG_FILE = __DIR__."/../../../../config/uvdesk_formbuilder.json"; public function loadFormsXHR(Request $request) { return new JsonResponse(json_decode(file_get_contents(self::CONFIG_FILE), true)); } public function removeFormConfiguration($id, Request $request) { $array = json_decode( file_get_contents(self::CONFIG_FILE), true); foreach($array as $key=>$value) { if($value['id'] == $id) { unset($array[$key]); } } file_put_contents(self::CONFIG_FILE, ''); file_put_contents(self::CONFIG_FILE, json_encode($array), FILE_APPEND); return new JsonResponse([ 'alertClass' => 'success', 'alertMessage' => 'Form configuration removed successfully.', ]); } } 12345678910111213141516171819202122232425262728293031 class FormBuilderChannelXHR extends Controller { const CONFIG_FILE = __DIR__."/../../../../config/uvdesk_formbuilder.json"; public function loadFormsXHR(Request $request) { return new JsonResponse(json_decode(file_get_contents(self::CONFIG_FILE), true)); } public function removeFormConfiguration($id, Request $request) { $array = json_decode( file_get_contents(self::CONFIG_FILE), true); foreach($array as $key=>$value) { if($value['id'] == $id) { unset($array[$key]); } } file_put_contents(self::CONFIG_FILE, ''); file_put_contents(self::CONFIG_FILE, json_encode($array), FILE_APPEND); return new JsonResponse([ 'alertClass' => 'success', 'alertMessage' => 'Form configuration removed successfully.', ]); } } FormBuilderChannelXHR comes into play when the front-end Backbone.js code wants to load configurations to display the form-builder channels, and when the user decides to delete a form-builder channel, so a ajax request is sent to this controller, calling the associated method, passing the necessary form-builder id, which is then processed by the controller to remove the desired form-builder configuration. The code for FormBuidlerChannel.php is as follows: class FormBuilderChannel extends Controller { const CONFIG_FILE = __DIR__."/../../../../config/uvdesk_formbuilder.json"; public function loadFormBuilders() { return $this->render('@UVDeskExtensionFramework//listConfigurations.html.twig', []); } public function createFormBuilderConfiguration(Request $request) { if($request->getMethod() == 'POST') { $params = $request->request->all(); $dataArray = ['id' => bin2hex(random_bytes(20)), 'form_name' => $params['form_name'], 'name' => (isset($params['name']) ? '1' : '0' ), 'type' => (isset($params['type']) ? '1' : '0' ), 'subject' => (isset($params['subject']) ? '1' : '0' ), 'gdpr' => (isset($params['gdpr']) ? '1' : '0' ), 'order_no' => (isset($params['order_no']) ? '1' : '0' ), 'file' => (isset($params['file']) ? '1' : '0' )]; $jsonData = json_decode(file_get_contents(self::CONFIG_FILE),true); array_push($jsonData,$dataArray); file_put_contents(self::CONFIG_FILE, ''); file_put_contents(self::CONFIG_FILE, json_encode($jsonData), FILE_APPEND); $this->addFlash('success', 'Form successfully created.'); return new RedirectResponse($this->generateUrl('formbuilder_settings')); } return $this->render('@UVDeskExtensionFramework//manageConfigurations.html.twig', [ 'formbuilder' => [], ]); } public function updateFormConfiguration($id, Request $request) { $config = null; $configs = json_decode(file_get_contents(self::CONFIG_FILE), true); foreach($configs as $key=>$value) { if($value['id'] == $id) { $config = $configs[$key]; } } if($request->getMethod() == 'POST') { $params = $request->request->all(); $configs = json_decode(file_get_contents(self::CONFIG_FILE), true); foreach($configs as $key=>$value) { if($value['id'] == $id) { $configs[$key]['form_name'] = ( empty($params['form_name']) ? $configs[$key]['form_name'] : $params['form_name'] ); $configs[$key]['name'] = (isset($params['name']) ? '1' : '0' ); $configs[$key]['type'] = (isset($params['type']) ? '1' : '0' ); $configs[$key]['subject'] = (isset($params['subject']) ? '1' : '0' ); $configs[$key]['gdpr'] = (isset($params['gdpr']) ? '1' : '0' ); $configs[$key]['order_no'] = (isset($params['order_no']) ? '1' : '0' ); $configs[$key]['file'] = (isset($params['file']) ? '1' : '0' ); } } file_put_contents(self::CONFIG_FILE, ''); file_put_contents(self::CONFIG_FILE, json_encode($configs), FILE_APPEND); $this->addFlash('success', 'Form successfully updated.'); return new RedirectResponse($this->generateUrl('formbuilder_settings')); } return $this->render('@UVDeskExtensionFramework//manageConfigurations.html.twig',[ 'formbuilder' => $config ?? null, ]); } public function previewForm($id, Request $request) { $array = json_decode(file_get_contents(self::CONFIG_FILE), true); $previewForm = null; foreach($array as $key=>$value) { if($value['id'] == $id) { $previewForm = $array[$key]; } } if( $request->getMethod() == "POST" ) { $params = $request->request->all(); $role = $this->getDoctrine()->getRepository(SupportRole::class)->find(4); $userInstance = $this->get('user.service')->createUserInstance($params['email'], $params['name'], $role); $params['from'] = $params['email']; $params['subject'] = ( isset($params['subject']) ? $params['subject'] : 'null'); $params['reply'] = $params['reply']; $params['fullname'] = $params['name']; $params['createdBy'] = 'customer'; $params['threadType'] = 'create'; $params['source'] = 'formbuilder'; $params['role'] = 4; $params['customer'] = $userInstance; $params['user'] = $userInstance; $params['type'] = $this->getDoctrine()->getRepository(TicketType::class)->find(1); $params['message'] = $params['reply']; $params['gdpr'] = ( isset($params['gdpr']) ? $params['gdpr'] : 'null'); $params['order_no'] = ( isset($params['order_no']) ? $params['order_no'] : 'null'); $params['file'] = ( isset($params['file']) ? $params['file'] : 'null'); $this->get('ticket.service')->createTicketBase($params); $this->addFlash('success', 'Ticket Created Successfully!'); return new RedirectResponse($this->generateUrl('helpdesk_member_ticket_collection')); } return $this->render('@UVDeskExtensionFramework//previewForm.html.twig', [ 'formbuilder' => $previewForm, ]); } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 class FormBuilderChannel extends Controller{ const CONFIG_FILE = __DIR__."/../../../../config/uvdesk_formbuilder.json"; public function loadFormBuilders() { return $this->render('@UVDeskExtensionFramework//listConfigurations.html.twig', []); } public function createFormBuilderConfiguration(Request $request) { if($request->getMethod() == 'POST') { $params = $request->request->all(); $dataArray = ['id' => bin2hex(random_bytes(20)), 'form_name' => $params['form_name'], 'name' => (isset($params['name']) ? '1' : '0' ), 'type' => (isset($params['type']) ? '1' : '0' ), 'subject' => (isset($params['subject']) ? '1' : '0' ), 'gdpr' => (isset($params['gdpr']) ? '1' : '0' ), 'order_no' => (isset($params['order_no']) ? '1' : '0' ), 'file' => (isset($params['file']) ? '1' : '0' )]; $jsonData = json_decode(file_get_contents(self::CONFIG_FILE),true); array_push($jsonData,$dataArray); file_put_contents(self::CONFIG_FILE, ''); file_put_contents(self::CONFIG_FILE, json_encode($jsonData), FILE_APPEND); $this->addFlash('success', 'Form successfully created.'); return new RedirectResponse($this->generateUrl('formbuilder_settings')); } return $this->render('@UVDeskExtensionFramework//manageConfigurations.html.twig', [ 'formbuilder' => [], ]); } public function updateFormConfiguration($id, Request $request) { $config = null; $configs = json_decode(file_get_contents(self::CONFIG_FILE), true); foreach($configs as $key=>$value) { if($value['id'] == $id) { $config = $configs[$key]; } } if($request->getMethod() == 'POST') { $params = $request->request->all(); $configs = json_decode(file_get_contents(self::CONFIG_FILE), true); foreach($configs as $key=>$value) { if($value['id'] == $id) { $configs[$key]['form_name'] = ( empty($params['form_name']) ? $configs[$key]['form_name'] : $params['form_name'] ); $configs[$key]['name'] = (isset($params['name']) ? '1' : '0' ); $configs[$key]['type'] = (isset($params['type']) ? '1' : '0' ); $configs[$key]['subject'] = (isset($params['subject']) ? '1' : '0' ); $configs[$key]['gdpr'] = (isset($params['gdpr']) ? '1' : '0' ); $configs[$key]['order_no'] = (isset($params['order_no']) ? '1' : '0' ); $configs[$key]['file'] = (isset($params['file']) ? '1' : '0' ); } } file_put_contents(self::CONFIG_FILE, ''); file_put_contents(self::CONFIG_FILE, json_encode($configs), FILE_APPEND); $this->addFlash('success', 'Form successfully updated.'); return new RedirectResponse($this->generateUrl('formbuilder_settings')); } return $this->render('@UVDeskExtensionFramework//manageConfigurations.html.twig',[ 'formbuilder' => $config ?? null, ]); } public function previewForm($id, Request $request) { $array = json_decode(file_get_contents(self::CONFIG_FILE), true); $previewForm = null; foreach($array as $key=>$value) { if($value['id'] == $id) { $previewForm = $array[$key]; } } if( $request->getMethod() == "POST" ) { $params = $request->request->all(); $role = $this->getDoctrine()->getRepository(SupportRole::class)->find(4); $userInstance = $this->get('user.service')->createUserInstance($params['email'], $params['name'], $role); $params['from'] = $params['email']; $params['subject'] = ( isset($params['subject']) ? $params['subject'] : 'null'); $params['reply'] = $params['reply']; $params['fullname'] = $params['name']; $params['createdBy'] = 'customer'; $params['threadType'] = 'create'; $params['source'] = 'formbuilder'; $params['role'] = 4; $params['customer'] = $userInstance; $params['user'] = $userInstance; $params['type'] = $this->getDoctrine()->getRepository(TicketType::class)->find(1); $params['message'] = $params['reply']; $params['gdpr'] = ( isset($params['gdpr']) ? $params['gdpr'] : 'null'); $params['order_no'] = ( isset($params['order_no']) ? $params['order_no'] : 'null'); $params['file'] = ( isset($params['file']) ? $params['file'] : 'null'); $this->get('ticket.service')->createTicketBase($params); $this->addFlash('success', 'Ticket Created Successfully!'); return new RedirectResponse($this->generateUrl('helpdesk_member_ticket_collection')); } return $this->render('@UVDeskExtensionFramework//previewForm.html.twig', [ 'formbuilder' => $previewForm, ]); } } As the name suggests, the function loadFormBuilders() is used to load/render the channels within our configuration files to the front-end. The function createFormBuilderConfiguration is used to process and save the configuration information of a form-builder channel into the configuration file. The function updateFormConfiguration comes into play when a user clicks the ‘edit’ option on a form-builder channel. After selecting the necessary fields to be placed within the form, the user clicks ‘Update FormBuilder’. This function processes the data, selects the form-builder channel with the specific id, updates and saves the configuration in the json file. The function previewForm() is used to render the form, with the desired fields, and when submitted, will generate a UVDesk ticket. This has been a brief introduction on the back-end aspect of creating a form-builder in Symfony 4. Stay tuned for the front-end side on the next installation. Cheers! I’ve explored this while contributing to a Symfony based project UVdesk. There are many more things to learn and implement; you can also contribute to our enterprise-level open source project helpdesk. Tag(s) application back-end configuration crud feature Form Form Builder programming Category(s) Symfony UVdesk