PHP项目Symfony form与图表联动

wen PHP项目 2

本文目录导读:

PHP项目Symfony form与图表联动

  1. 数据准备与图表库选择
  2. 步骤一:创建表单(筛选条件)
  3. 步骤二:编写 Ajax 路由(异步刷新图表)
  4. 步骤三:Twig 模板(表单 + Chart.js 图表)
  5. 关键注意事项
  6. 进阶:使用 Symfony Form 组件 + Ajax 提交(完整 CSRF)
  7. 最终效果与总结

在 Symfony 项目中实现 Form 与图表的联动,通常指的是通过表单的筛选条件(如日期范围、类别、状态等)来动态更新图表数据。
这种联动常见的实现方式有三种:页面刷新(传统)Ajax 异步请求(常用)、以及 WebSocket/PHP SSE 实时推送

以下是一个 Symfony 6/7 + Chart.js 的完整实战方案(推荐 Ajax 方式),包含后端处理逻辑、前端实现和关键注意事项。


数据准备与图表库选择

  • 图表库:推荐使用 Chart.jsHighcharts(商业授权)。
  • Symfony 版本:6.4+ 或 7.x,使用 Twig 模板和 Doctrine。

步骤一:创建表单(筛选条件)

假设我们有一个 SalesController,展示销售额图表,筛选条件为日期范围和产品分类。

在 Controller 中定义表单

// src/Controller/SalesController.php
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class SalesController extends AbstractController
{
    public function index(Request $request): Response
    {
        // 1. 构建筛选表单
        $form = $this->createFormBuilder()
            ->add('startDate', DateType::class, [
                'widget' => 'single_text',
                'required' => false,
                'data' => new \DateTime('-30 days'),
            ])
            ->add('endDate', DateType::class, [
                'widget' => 'single_text',
                'required' => false,
                'data' => new \DateTime('now'),
            ])
            ->add('category', ChoiceType::class, [
                'choices' => [
                    '电子产品' => 'electronics',
                    '服装' => 'clothing',
                    '食品' => 'food',
                ],
                'placeholder' => '全部分类',
                'required' => false,
            ])
            ->add('submit', SubmitType::class, ['label' => '筛选'])
            ->getForm();
        $form->handleRequest($request);
        // 2. 初始加载时获取默认数据(用于首次图表渲染)
        $chartData = $this->getChartData($form->getData());
        return $this->render('sales/index.html.twig', [
            'form' => $form->createView(),
            'chartData' => json_encode($chartData),
        ]);
    }
    // 图表数据查询方法(数据源)
    private function getChartData(?array $filterData): array
    {
        // 模拟数据库查询,实际请替换为 Repository 查询
        // 这里用假数据示意
        return [
            'labels' => ['1月', '2月', '3月', '4月', '5月'],
            'datasets' => [
                [
                    'label' => '销售额',
                    'data' => [12000, 15000, 13500, 17000, 16000],
                    'backgroundColor' => 'rgba(54, 162, 235, 0.2)',
                    'borderColor' => 'rgba(54, 162, 235, 1)',
                ],
            ],
        ];
    }
}

步骤二:编写 Ajax 路由(异步刷新图表)

为表单提交创建一个异步端点,用于返回 JSON 格式的图表数据。

在同一个 Controller 中添加方法

// 仍在 SalesController 中
#[Route('/sales/chart-data', name: 'sales_chart_data', methods: ['POST'])]
public function chartData(Request $request): JsonResponse
{
    // 接收表单提交的数据(JSON 格式或标准 POST)
    $data = json_decode($request->getContent(), true) ?? $request->request->all();
    // 这里也可以重新创建表单并 handleRequest,但更简洁的方式是直接接收集合
    // 注意安全验证:对接收参数做基本过滤
    $filterData = [
        'startDate' => $data['startDate'] ?? null,
        'endDate'   => $data['endDate'] ?? null,
        'category'  => $data['category'] ?? null,
    ];
    // 调用数据查询方法(真实项目应调用 Repository)
    $chartData = $this->getChartData($filterData);
    return new JsonResponse($chartData);
}

提示:你也可以复用之前定义的 Form 类型,但要注意 handleRequest 默认不支持 JSON 请求头,对于 Ajax 提交,一般传递 JSON 或用 FormFactory::createNamed 配合请求数据。


步骤三:Twig 模板(表单 + Chart.js 图表)

templates/sales/index.html.twig

{% extends 'base.html.twig' %}
{% block title %}销售图表联动{% endblock %}
{% block body %}
<div class="container mt-4">
    <h1>销售数据筛选</h1>
    {# 表单区域 #}
    {{ form_start(form, {'attr': {'id': 'filter-form'}}) }}
        <div class="row g-3 align-items-end">
            <div class="col-auto">
                {{ form_label(form.startDate) }}
                {{ form_widget(form.startDate) }}
            </div>
            <div class="col-auto">
                {{ form_label(form.endDate) }}
                {{ form_widget(form.endDate) }}
            </div>
            <div class="col-auto">
                {{ form_label(form.category) }}
                {{ form_widget(form.category) }}
            </div>
            <div class="col-auto">
                {{ form_widget(form.submit, {'attr': {'class': 'btn btn-primary'}}) }}
            </div>
        </div>
    {{ form_end(form) }}
    {# 图表容器 #}
    <div class="mt-5">
        <canvas id="salesChart" width="800" height="400"></canvas>
    </div>
</div>
{# 引入 Chart.js #}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
    // 从 Twig 传递的初始数据(PHP json_encode)
    const initialChartData = {{ chartData|raw }};
    // 渲染初始图表
    const ctx = document.getElementById('salesChart').getContext('2d');
    let salesChart = new Chart(ctx, {
        type: 'bar', // or 'line', 'pie' 等
        data: initialChartData,
        options: {
            responsive: true,
            scales: {
                y: {
                    beginAtZero: true
                }
            }
        }
    });
    // ----- 表单提交联动(Ajax) -----
    const form = document.getElementById('filter-form');
    form.addEventListener('submit', function(event) {
        event.preventDefault(); // 阻止默认提交
        // 收集表单数据(包括 startDate, endDate, category)
        const formData = new FormData(form);
        const jsonData = {};
        formData.forEach((value, key) => {
            // 去除 form 前缀(form[startDate] 需要处理)
            // 实际表单 name 可能是 form[startDate],这里做转换
            const cleanKey = key.replace(/^form\[/, '').replace(/\]$/, '');
            jsonData[cleanKey] = value;
        });
        // 发送 POST 请求到 Ajax 端点
        fetch('{{ path('sales_chart_data') }}', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Requested-With': 'XMLHttpRequest'
            },
            body: JSON.stringify(jsonData)
        })
        .then(response => response.json())
        .then(data => {
            // 更新图表数据
            salesChart.data = data;
            salesChart.update();
        })
        .catch(error => console.error('获取图表数据失败:', error));
    });
</script>
{% endblock %}

关键注意事项

问题 解决方案
表单 name 前缀 Symfony 默认添加前缀(如 form[startDate]),在 JS 中可用 replace(/^form\[/, '').replace(/\]$/, '') 去除,或设置表单的 block_prefix 为无前缀,更推荐在 PHP 端直接处理 $request->request->all() 而不使用表单组件。
CSRF 保护 如果使用 form_start 会自动生成 CSRF token,在 Ajax 提交时,需要将 token 字段一并发送,简单做法:在 form 中添加一个隐藏字段 _token,并在 JS 中获取。
日期格式 表单使用 DateType::class + single_text 会输出 YYYY-MM-DD,后端接收可直接 new \DateTime($value),注意处理 null 值。
性能优化 真实项目中避免在 Controller 中直接写查询,应将逻辑移至 Repository 或 Service,对于大量数据,可考虑图表数据缓存(如 Redis)或只返回聚合数据。
安全验证 Ajax 端点应验证用户权限(如 isGranted),并对输入参数做合法性检查,避免 SQL 注入。
实时性要求高 如果是仪表盘需要实时更新,可考虑使用 Symfony MercureWebSocket(Ratchet),由服务器推送数据到前端 Chart.js。

进阶:使用 Symfony Form 组件 + Ajax 提交(完整 CSRF)

如果希望完全使用 Symfony Form 处理,可以在 Controller 中这样写:

// chartData 方法内部
$form = $this->createForm(FilterType::class); // 自定义 FormType
$form->handleRequest($request);
// 处理 form 提交的数据
if ($form->isSubmitted() && $form->isValid()) {
    $data = $form->getData();
    // ...
}

前端的 JS 需将 form 序列化(包括所有字段 + _token)。
可以借助 new FormData(form) 直接发送,PHP 端使用 $request->request->all() 也能接收到。


最终效果与总结

  • 用户修改表单筛选条件并点击“筛选”。
  • Ajax 请求携带条件到 chartData 端点。
  • 后端返回新的 labelsdatasets 数据。
  • 前端 Chart.js 实例更新图表,无需页面刷新。

这种模式非常适用于 报表系统、数据监控看板、管理后台统计 等场景。
如果还需要“图表点击联动表格”或“图表与地图联动”,核心思路类似:通过前端事件监听 + Ajax 交互即可实现。

抱歉,评论功能暂时关闭!