本文目录导读:

我来详细介绍 Symfony 中处理表单和统计报表的实现方法。
Symfony Form 基础
创建表单类
<?php
// src/Form/ReportFilterType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
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\OptionsResolver\OptionsResolver;
class ReportFilterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('startDate', DateType::class, [
'widget' => 'single_text',
'label' => '开始日期',
'required' => true
])
->add('endDate', DateType::class, [
'widget' => 'single_text',
'label' => '结束日期',
'required' => true
])
->add('category', ChoiceType::class, [
'label' => '分类',
'choices' => [
'全部' => '',
'产品A' => 'product_a',
'产品B' => 'product_b',
'产品C' => 'product_c'
],
'required' => false
])
->add('submit', SubmitType::class, [
'label' => '生成报表'
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => null,
'csrf_protection' => true
]);
}
}
Controller 中使用表单
<?php
// src/Controller/ReportController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Form\ReportFilterType;
use App\Service\ReportService;
class ReportController extends AbstractController
{
#[Route('/reports', name: 'reports_index')]
public function index(Request $request, ReportService $reportService): Response
{
$form = $this->createForm(ReportFilterType::class);
$form->handleRequest($request);
$reportData = [];
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$reportData = $reportService->generateReport(
$data['startDate'],
$data['endDate'],
$data['category']
);
}
return $this->render('report/index.html.twig', [
'form' => $form->createView(),
'reportData' => $reportData
]);
}
}
统计报表实现
报表服务层
<?php
// src/Service/ReportService.php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use App\Repository\OrderRepository;
class ReportService
{
private EntityManagerInterface $entityManager;
private OrderRepository $orderRepository;
public function __construct(
EntityManagerInterface $entityManager,
OrderRepository $orderRepository
) {
$this->entityManager = $entityManager;
$this->orderRepository = $orderRepository;
}
/**
* 生成统计报表
*/
public function generateReport(\DateTime $startDate, \DateTime $endDate, ?string $category = null): array
{
// 基础统计
$basicStats = $this->getBasicStats($startDate, $endDate);
// 按分类统计
$categoryStats = $this->getCategoryStats($startDate, $endDate, $category);
// 按日期统计(用于图表)
$dailyStats = $this->getDailyStats($startDate, $endDate);
return [
'basic' => $basicStats,
'category' => $categoryStats,
'daily' => $dailyStats,
'period' => [
'start' => $startDate,
'end' => $endDate
]
];
}
private function getBasicStats(\DateTime $start, \DateTime $end): array
{
$conn = $this->entityManager->getConnection();
$sql = "
SELECT
COUNT(*) as total_orders,
SUM(total_amount) as total_revenue,
AVG(total_amount) as avg_order_value,
COUNT(DISTINCT user_id) as unique_customers
FROM orders
WHERE created_at BETWEEN :start AND :end
AND status NOT IN ('cancelled', 'refunded')
";
$stmt = $conn->executeQuery($sql, [
'start' => $start->format('Y-m-d H:i:s'),
'end' => $end->format('Y-m-d H:i:s')
]);
return $stmt->fetchAssociative();
}
private function getCategoryStats(\DateTime $start, \DateTime $end, ?string $category = null): array
{
$conn = $this->entityManager->getConnection();
$sql = "
SELECT
p.category,
COUNT(*) as order_count,
SUM(oi.quantity) as total_quantity,
SUM(oi.subtotal) as total_revenue
FROM orders o
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.created_at BETWEEN :start AND :end
AND o.status NOT IN ('cancelled', 'refunded')
";
$params = [
'start' => $start->format('Y-m-d H:i:s'),
'end' => $end->format('Y-m-d H:i:s')
];
if ($category) {
$sql .= " AND p.category = :category";
$params['category'] = $category;
}
$sql .= " GROUP BY p.category ORDER BY total_revenue DESC";
$stmt = $conn->executeQuery($sql, $params);
return $stmt->fetchAllAssociative();
}
private function getDailyStats(\DateTime $start, \DateTime $end): array
{
$conn = $this->entityManager->getConnection();
$sql = "
SELECT
DATE(created_at) as date,
COUNT(*) as order_count,
SUM(total_amount) as revenue
FROM orders
WHERE created_at BETWEEN :start AND :end
AND status NOT IN ('cancelled', 'refunded')
GROUP BY DATE(created_at)
ORDER BY date ASC
";
$stmt = $conn->executeQuery($sql, [
'start' => $start->format('Y-m-d H:i:s'),
'end' => $end->format('Y-m-d H:i:s')
]);
return $stmt->fetchAllAssociative();
}
}
Twig 模板展示
{# templates/report/index.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}统计报表{% endblock %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/chart.js@3/dist/chart.min.css">
<style>
.stat-card {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #333;
}
.stat-label {
color: #666;
font-size: 14px;
}
</style>
{% endblock %}
{% block body %}
<div class="container-fluid">
<h1 class="mb-4">统计报表</h1>
<!-- 筛选表单 -->
<div class="card mb-4">
<div class="card-body">
{{ form_start(form)}}
<div class="row">
<div class="col-md-3">{{ form_row(form.startDate) }}</div>
<div class="col-md-3">{{ form_row(form.endDate) }}</div>
<div class="col-md-3">{{ form_row(form.category) }}</div>
<div class="col-md-3 d-flex align-items-end">
{{ form_row(form.submit) }}
</div>
</div>
{{ form_end(form) }}
</div>
</div>
{% if reportData is not empty %}
<!-- 基础统计卡片 -->
<div class="row mb-4">
<div class="col-md-3">
<div class="stat-card">
<div class="stat-label">总订单数</div>
<div class="stat-value">{{ reportData.basic.total_orders|default(0) }}</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<div class="stat-label">总收入</div>
<div class="stat-value">¥{{ reportData.basic.total_revenue|default(0)|number_format(2) }}</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<div class="stat-label">平均订单金额</div>
<div class="stat-value">¥{{ reportData.basic.avg_order_value|default(0)|number_format(2) }}</div>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<div class="stat-label">独立客户数</div>
<div class="stat-value">{{ reportData.basic.unique_customers|default(0) }}</div>
</div>
</div>
</div>
<!-- 图表区域 -->
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5>每日收入趋势</h5>
</div>
<div class="card-body">
<canvas id="revenueChart" height="300"></canvas>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5>分类占比</h5>
</div>
<div class="card-body">
<canvas id="categoryChart" height="300"></canvas>
</div>
</div>
</div>
</div>
<!-- 分类统计表格 -->
<div class="card mt-4">
<div class="card-header">
<h5>分类统计详情</h5>
</div>
<div class="card-body">
<table class="table table-striped">
<thead>
<tr>
<th>分类</th>
<th>订单数</th>
<th>销售数量</th>
<th>销售额</th>
</tr>
</thead>
<tbody>
{% for stat in reportData.category %}
<tr>
<td>{{ stat.category }}</td>
<td>{{ stat.order_count }}</td>
<td>{{ stat.total_quantity }}</td>
<td>¥{{ stat.total_revenue|number_format(2) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script src="https://cdn.jsdelivr.net/npm/chart.js@3/dist/chart.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
{% if reportData.daily is not empty %}
// 收入趋势图
const dailyData = {{ reportData.daily|json_encode|raw }};
const ctx1 = document.getElementById('revenueChart').getContext('2d');
new Chart(ctx1, {
type: 'line',
data: {
labels: dailyData.map(item => item.date),
datasets: [{
label: '收入',
data: dailyData.map(item => item.revenue),
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false
}
});
{% endif %}
{% if reportData.category is not empty %}
// 分类占比图
const categoryData = {{ reportData.category|json_encode|raw }};
const ctx2 = document.getElementById('categoryChart').getContext('2d');
new Chart(ctx2, {
type: 'pie',
data: {
labels: categoryData.map(item => item.category),
datasets: [{
data: categoryData.map(item => item.total_revenue),
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)'
]
}]
}
});
{% endif %}
});
</script>
{% endblock %}
高级报表功能
导出 PDF 报表
<?php
// src/Service/PdfReportService.php
namespace App\Service;
use Dompdf\Dompdf;
use Twig\Environment;
class PdfReportService
{
private Environment $twig;
public function __construct(Environment $twig)
{
$this->twig = $twig;
}
public function generatePdf(array $data): string
{
$html = $this->twig->render('report/pdf_template.html.twig', [
'reportData' => $data,
'generatedAt' => new \DateTime()
]);
$dompdf = new Dompdf();
$dompdf->loadHtml($html);
$dompdf->setPaper('A4', 'landscape');
$dompdf->render();
return $dompdf->output();
}
}
视图构建器模式
<?php
// src/Report/ReportBuilder.php
namespace App\Report;
class ReportBuilder
{
private array $data = [];
private array $charts = [];
private array $tables = [];
public function addMetric(string $label, $value, string $format = null): self
{
$this->data['metrics'][] = [
'label' => $label,
'value' => $value,
'format' => $format
];
return $this;
}
public function addChart(string $type, array $data, array $options = []): self
{
$this->charts[] = [
'type' => $type,
'data' => $data,
'options' => $options
];
return $this;
}
public function addTable(string $title, array $headers, array $rows): self
{
$this->tables[] = [
'title' => $title,
'headers' => $headers,
'rows' => $rows
];
return $this;
}
public function build(): array
{
return [
'data' => $this->data,
'charts' => $this->charts,
'tables' => $this->tables
];
}
}
这些代码提供了 Symfony 项目中实现表单过滤和统计报表的完整解决方案,包括:
- 表单创建和验证
- 数据查询和聚合
- 图表可视化
- PDF 导出功能
- 可扩展的报表构建器模式
你可以根据具体需求调整查询逻辑和展示方式。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播