本文目录导读:

- 数据准备与图表库选择
- 步骤一:创建表单(筛选条件)
- 步骤二:编写 Ajax 路由(异步刷新图表)
- 步骤三:Twig 模板(表单 + Chart.js 图表)
- 关键注意事项
- 进阶:使用 Symfony Form 组件 + Ajax 提交(完整 CSRF)
- 最终效果与总结
在 Symfony 项目中实现 Form 与图表的联动,通常指的是通过表单的筛选条件(如日期范围、类别、状态等)来动态更新图表数据。
这种联动常见的实现方式有三种:页面刷新(传统)、Ajax 异步请求(常用)、以及 WebSocket/PHP SSE 实时推送。
以下是一个 Symfony 6/7 + Chart.js 的完整实战方案(推荐 Ajax 方式),包含后端处理逻辑、前端实现和关键注意事项。
数据准备与图表库选择
- 图表库:推荐使用 Chart.js 或 Highcharts(商业授权)。
- 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 Mercure 或 WebSocket(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端点。 - 后端返回新的
labels和datasets数据。 - 前端 Chart.js 实例更新图表,无需页面刷新。
这种模式非常适用于 报表系统、数据监控看板、管理后台统计 等场景。
如果还需要“图表点击联动表格”或“图表与地图联动”,核心思路类似:通过前端事件监听 + Ajax 交互即可实现。