You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 18:25:53