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

Symfony6+TinyMCE富文本textarea实现及图片持久化问题

Symfony + TinyMCE 富文本图片URL持久化解决方案

问题核心

你需要实现Symfony结合TinyMCE的富文本编辑器,支持多图片上传、文本样式渲染,最终将包含正确图片URL的富文本内容持久化到数据库。当前配置中TinyMCE已开启图片上传,但后端未正确处理图片上传请求,导致富文本中的图片无法替换为服务器可访问的URL。

分步解决方案

1. 新增TinyMCE图片上传专用控制器

创建独立路由处理图片上传请求,返回TinyMCE要求的格式(包含location字段的JSON),确保上传后的图片生成可访问URL。

#[Route('/admin/tinymce/upload', name: 'app_admin_tinymce_upload', methods: ['POST'])]
public function tinymceUpload(Request $request, UploadHandler $uploadHandler, UrlGeneratorInterface $urlGenerator): JsonResponse
{
    $file = $request->files->get('file');
    if (!$file || !$file instanceof UploadedFile) {
        return new JsonResponse(['error' => 'Aucun fichier téléchargé'], 400);
    }

    // 调用你的上传处理器保存图片,调整参数匹配你的业务逻辑
    $fileName = $uploadHandler->uploadFile($file, 'tinymce_images');
    // 生成图片的绝对访问URL
    $imageUrl = $urlGenerator->generate('app_image_tinymce', ['fileName' => $fileName], UrlGeneratorInterface::ABSOLUTE_URL);

    // TinyMCE要求返回的JSON必须包含location字段
    return new JsonResponse(['location' => $imageUrl]);
}

2. 配置图片访问路由

添加路由用于前端访问上传后的图片:

#[Route('/images/tinymce/{fileName}', name: 'app_image_tinymce')]
public function serveTinymceImage(string $fileName, Filesystem $filesystem): Response
{
    $filePath = $this->getParameter('tinymce_images_directory') . '/' . $fileName;
    
    if (!$filesystem->exists($filePath)) {
        throw $this->createNotFoundException('Image not found');
    }
    
    $mimeType = mime_content_type($filePath);
    return new BinaryFileResponse($filePath, 200, ['Content-Type' => $mimeType]);
}

在services.yaml中配置图片存储目录参数:

parameters:
    tinymce_images_directory: '%kernel.project_dir%/public/images/tinymce'

3. 修改TinyMCE配置

将images_upload_url替换为实际的上传路由,确保编辑器自动上传图片并替换为服务器URL:

<script type="text/javascript" src="https://cdn.tiny.cloud/1/000e98r5ejnk39y3tkknc2y0b8qh895bn5qv1jrc6a5v3ceu/tinymce/5/tinymce.min.js"></script>
<script>
    tinymce.init({
        selector: '#portrait_form_description',
        language: 'fr',
        plugins: 'image',
        toolbar: 'undo redo | link image',
        image_title: true,
        automatic_uploads: true,
        valid_elements : '+*[*]',
        // 替换为上传路由的Twig路径
        images_upload_url: "{{ path('app_admin_tinymce_upload') }}",
        file_picker_types: 'image',
        file_picker_callback: function (cb, value, meta) {
            var input = document.createElement('input');
            input.setAttribute('type', 'file');
            input.setAttribute('accept', 'image/*');

            input.onchange = function () {
                var file = this.files[0];

                var reader = new FileReader();
                reader.onload = function () {
                    var id = 'blobid' + (new Date()).getTime();
                    var blobCache =  tinymce.activeEditor.editorUpload.blobCache;
                    var base64 = reader.result.split(',')[1];
                    var blobInfo = blobCache.create(id, file, base64);
                    blobCache.add(blobInfo);

                    cb(blobInfo.blobUri(), { title: file.name });
                };
                reader.readAsDataURL(file);
            };

            input.click();
        },
        content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }'
    });
</script>

4. 调整现有控制器逻辑

移除与富文本无关的独立图片处理代码,直接保存包含正确图片URL的富文本内容:

#[Route('/admin/portraits', name: 'app_admin_portraits')]
public function portraits(Request $request): Response
{
    $portrait = new Portrait();
    $form = $this->createForm(PortraitFormType::class, $portrait);
    
    if($request->isXmlHttpRequest()){
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            // 此时description字段已包含服务器可访问的图片URL,直接持久化
            $this->em->persist($portrait);
            $this->em->flush();
            
            $this->addFlash('success', "Portrait enregistré avec succéss");
            return new JsonResponse(['success' => true, 'message' => "Portrait enregistré avec succès"]);
        } else {
            $errors = $this->getFormErrors($form);
            return new JsonResponse(['success' => false, 'errors' => $errors], 400);
        }
    }

    $portraits = $this->paginator->paginate($this->portraitRepository->findAll(), 1, 10);
    
    return $this->render('admin/portraits.html.twig', [
        'formPortrait' => $form->createView(),
        'portraits' => $portraits,
    ]);
}

5. 确保实体字段支持长文本

在Portrait实体中,将description字段设置为text类型以存储富文本内容:

#[ORM\Column(type: 'text')]
private ?string $description = null;

public function getDescription(): ?string
{
    return $this->description;
}

public function setDescription(?string $description): self
{
    $this->description = $description;
    return $this;
}

流程说明

  1. 用户在编辑器中插入图片时,TinyMCE调用file_picker_callback选择本地文件
  2. 编辑器自动将文件上传到配置的images_upload_url路由
  3. 后端处理上传,返回图片的绝对访问URL
  4. TinyMCE将编辑器中的Blob URI替换为返回的URL
  5. 表单提交时,description字段已包含正确的图片URL,直接保存到数据库即可

内容的提问来源于stack exchange,提问作者kasali

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 17:46:00