在Symfony控制器中通过双路由更新表单遇问题求助
Symfony表单更新问题排查与修复
问题背景
使用Symfony开发时,通过两个路由更新表单遇到异常:点击按钮后,无法将Monaco Editor中的JSON数据转换为带PHP注解的自定义JSON,也无法生成Bootstrap手风琴形式的fieldDefinitions表单(基于EasyAdmin);同时需结合Twig编辑器和mPDF实现PDF预览,当前表单更新逻辑完全失效。
现有代码
Twig模板代码
{% block main %} <div class="editor-wrapper" id="json-editor-wrapper"> <div class="editor" id="json-editor"></div> </div> <button class="btn-secondary btn" id="apply-button" onclick="applyFields();">⬆️ Felder zu Config ⬇️</button> <div class="field-definitions-wrapper"> {{ form(fieldDefinitionsForm) }} </div> <script> function applyFields() { let jsonData; const monacoScript = document.createElement('script'); monacoScript.src = 'https://cdn.jsdelivr.net/npm/monaco-editor/min/vs/loader.js'; monacoScript.async = true; monacoScript.onload = function() { require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor/min/vs' } }); require(['vs/editor/editor.main'], function() { jsonData = monaco.editor.getEditors()[0].getValue(); fetch('/updateFields', { method: 'POST', body: JSON.stringify({ fields: jsonData }), headers: { 'Content-Type': 'application/json', }, }) .then(response => response.json()) .then(data => { console.log(data.fieldData); }) .catch(error => { console.error('Error updating fields: ', error); }); }); }; document.head.appendChild(monacoScript); } </script> {% endblock %}
控制器代码
class TwigToPdfController extends AbstractController { use WithTwigEnvironmentTrait; use UseTokenizedConfigurationTrait; public const ROUTE_PANEL_ITEM = 'twigToPdf'; public LoggerInterface $logger; private MessageStructureDefinition $msd; public function __construct(LoggerInterface $logger) { $this->logger = $logger; $this->msd = new MessageStructureDefinition(); } #[Route('/renderPdf', name: self::ROUTE_PANEL_ITEM)] // Initial render of pdf on loading the page. public function renderPdf(Request $request): Response { $fieldDefinitions = $this->render('field_definitions/field_definitions.html.twig', [ 'fieldDefinitionsForm' => $this->createForm(FieldDefinitionsFormType::class, $this->msd)->createView() ]); $twigTemplate = $this->twig->createTemplate(""); // Empty twig template string $renderedTemplate = $twigTemplate->render([]); // Resolve twig template to pure html $mpdf = new Mpdf(); $mpdf->WriteHTML($renderedTemplate); $pdf = $mpdf->Output('', dest: Destination::STRING_RETURN); $pdfDataUrl = 'data:application/pdf;base64,' . base64_encode($pdf); $pdfPreview = $this->renderView('pdfpreview/pdfpreview.html.twig', [ 'preview' => $pdfDataUrl ]); // Render the parent template and include child templates as variables return $this->render('twig_to_pdf/twigToPdf.html.twig', [ 'fieldDefinitions' => $fieldDefinitions, 'pdfPreview' => $pdfPreview ]); } #[Route('/updateFields')] public function updateFields(Request $request): JsonResponse { // Decode the JSON data into a PHP array $requestData = json_decode($request->getContent(), true); if ($requestData === null && json_last_error() !== JSON_ERROR_NONE) { // Handle JSON decoding errors return new JsonResponse(['error' => 'Invalid JSON data'], Response::HTTP_BAD_REQUEST); } // Access the "fields" key to get the JSON data $fieldData = json_decode($requestData['fields'], true); return new JsonResponse([ 'fieldData' => $fieldData ]); } #[Route('/updatePdf')] // Updates pdf by filling it with header, body/content & footer. public function updatePdf(Request $request): JsonResponse { $requestData = json_decode($request->getContent() ?? '{}'); // Parse JSON data /* * Generate PDF using mPDF based on $headerContent, $bodyContent, $footerContent. * Those need to be the final html which results of the resolved twig data. */ $mpdf = new Mpdf(); $mpdf->SetHTMLHeader($this->twig->createTemplate($requestData->header)->render((array)$requestData->twigParams)); $mpdf->WriteHTML($this->twig->createTemplate($requestData->body)->render((array)$requestData->twigParams)); $mpdf->SetHTMLFooter($this->twig->createTemplate($requestData->footer)->render((array)$requestData->twigParams)); try { // Output the PDF as a string $pdfData = $mpdf->Output('', 'S'); // 'S' option generates the PDF as a string // Return the PDF data as JSON return new JsonResponse([ 'pdfData' => base64_encode($pdfData) ]); } catch (\Exception $e) { // Handle PDF generation error return new JsonResponse(['error' => 'PDF generation failed'], Response::HTTP_INTERNAL_SERVER_ERROR); } } }
问题排查与修复方案
1. 前端核心问题修复:重复加载Monaco + 无DOM更新逻辑
当前applyFields函数每次点击都会重新加载Monaco脚本,导致编辑器实例获取异常;且后端返回数据后,未更新表单DOM。
修复后前端脚本:
// 页面加载时初始化Monaco,避免重复加载 require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor/min/vs' } }); require(['vs/editor/editor.main'], function() { monaco.editor.create(document.getElementById('json-editor'), { value: '', language: 'json' }); }); function applyFields() { const editor = monaco.editor.getEditors()[0]; if (!editor) { console.error('Monaco Editor未初始化'); return; } const jsonData = editor.getValue(); fetch('/updateFields', { method: 'POST', body: JSON.stringify({ fields: jsonData }), headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content }, }) .then(response => { if (!response.ok) throw new Error('请求失败'); return response.json(); }) .then(data => { if (data.error) { console.error(data.error); return; } // 重新请求渲染后的表单HTML并替换DOM fetch('/renderPdf', { method: 'POST', headers: { 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content } }) .then(res => res.text()) .then(html => { const tempDiv = document.createElement('div'); tempDiv.innerHTML = html; const newForm = tempDiv.querySelector('.field-definitions-wrapper').innerHTML; document.querySelector('.field-definitions-wrapper').innerHTML = newForm; }); }) .catch(error => { console.error('更新字段失败: ', error); }); }
2. 后端核心问题修复:未更新数据源 + 缺失CSRF保护
当前updateFields路由仅返回解析后的JSON,未更新MessageStructureDefinition实例;且路由无CSRF保护,会导致POST请求被拦截。
修复后控制器代码:
// 为updateFields添加CSRF保护与POST方法限制 #[Route('/updateFields', methods: ['POST'])] public function updateFields(Request $request): JsonResponse { $requestData = json_decode($request->getContent(), true); if ($requestData === null && json_last_error() !== JSON_ERROR_NONE) { return new JsonResponse(['error' => '无效的JSON数据'], Response::HTTP_BAD_REQUEST); } $fieldData = json_decode($requestData['fields'], true); if ($fieldData === null) { return new JsonResponse(['error' => '字段JSON解析失败'], Response::HTTP_BAD_REQUEST); } // 更新表单数据源(需根据MessageStructureDefinition实际方法调整) $this->msd->setFields($fieldData); return new JsonResponse([ 'fieldData' => $fieldData ]); } // 修改renderPdf支持POST请求,用于重新渲染表单 #[Route('/renderPdf', name: self::ROUTE_PANEL_ITEM, methods: ['GET', 'POST'])] public function renderPdf(Request $request): Response { // 原有PDF生成逻辑不变,此时msd已为更新后的数据 $fieldDefinitions = $this->render('field_definitions/field_definitions.html.twig', [ 'fieldDefinitionsForm' => $this->createForm(FieldDefinitionsFormType::class, $this->msd)->createView() ]); $twigTemplate = $this->twig->createTemplate(""); $renderedTemplate = $twigTemplate->render([]); $mpdf = new Mpdf(); $mpdf->WriteHTML($renderedTemplate); $pdf = $mpdf->Output('', dest: Destination::STRING_RETURN); $pdfDataUrl = 'data:application/pdf;base64,' . base64_encode($pdf); $pdfPreview = $this->renderView('pdfpreview/pdfpreview.html.twig', [ 'preview' => $pdfDataUrl ]); return $this->render('twig_to_pdf/twigToPdf.html.twig', [ 'fieldDefinitions' => $fieldDefinitions, 'pdfPreview' => $pdfPreview ]); }
3. 额外配置
- 在Twig模板的
<head>中添加CSRF令牌标签:<meta name="csrf-token" content="{{ csrf_token('authenticate') }}"> - 确保
FieldDefinitionsFormType正确映射MessageStructureDefinition的字段,能生成对应的手风琴结构表单;若使用EasyAdmin,需符合其表单配置规范。
内容的提问来源于stack exchange,提问作者Proudyy
相关产品推荐
相关产品推荐

