PHP项目Symfony form与统计报表

wen PHP项目 2

本文目录导读:

PHP项目Symfony form与统计报表

  1. Symfony Form 基础
  2. 统计报表实现
  3. 高级报表功能

我来详细介绍 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 导出功能
  • 可扩展的报表构建器模式

你可以根据具体需求调整查询逻辑和展示方式。

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