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

Symfony下PHP执行时间测量及3.4版本批量发件超时解决方案问询

我来帮你一步步搞定这两个Symfony 3.4里的常见问题:

一、在Symfony 3.4中测量PHP执行时间

这里有两种实用的方法,根据你的场景选就行:

方法1:用Symfony自带的Stopwatch组件(推荐)

这是官方主推的计时工具,适合跟踪代码块的执行时间,不管是控制器逻辑还是服务方法都能用,还能拆分多个计时阶段。

步骤很简单:

  • 先在类里注入Stopwatch服务
  • 启动计时器、执行代码、停止计时后就能拿到精确耗时

示例代码(控制器里的用法):

// src/AppBundle/Controller/TestController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\HttpFoundation\Response;

class TestController extends Controller
{
    public function timingAction(Stopwatch $stopwatch)
    {
        // 启动一个名为"heavy-task"的计时器
        $stopwatch->start('heavy-task');
        
        // 这里放你要测量的代码逻辑
        sleep(2);
        // ...
        
        // 停止计时并获取事件对象
        $event = $stopwatch->stop('heavy-task');
        
        // 获取执行时间(毫秒)
        $executionTime = $event->getDuration();
        
        return new Response("代码执行时间:{$executionTime} 毫秒");
    }
}

方法2:手动计时(简单场景)

如果只是快速测试某段小代码,不想引入组件,用microtime()手动计时就足够简单直接:

$startTime = microtime(true);

// 你的代码逻辑
for ($i=0; $i<10000; $i++) {
    // ...
}

$endTime = microtime(true);
$executionTime = ($endTime - $startTime) * 1000; // 转换为毫秒

echo "执行时间:{$executionTime} ms";
二、解决大量邮件发送超时问题(队列+AJAX进度更新)

你的思路完全靠谱!把邮件存入队列分批次处理,再用AJAX更新进度,完美避开PHP执行时间限制。下面是具体实现步骤:

1. 创建邮件队列实体(对应email_queue表)

先生成一个EmailQueue实体,用来存储待发送的邮件信息,需要的字段可以参考:

  • id(主键)
  • recipient(收件人邮箱)
  • subject(邮件主题)
  • content(邮件内容)
  • status(状态:pending/sent/failed)
  • createdAt(创建时间)
  • sentAt(发送时间)

用Doctrine命令快速生成:

php bin/console doctrine:generate:entity

跟着提示填写实体名称和字段即可,最后别忘了执行迁移生成表:

php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate

2. 编写邮件队列处理服务

创建一个专门的服务,负责每次取出一批待发送邮件,发送后更新状态:

// src/AppBundle/Service/EmailQueueProcessor.php
namespace AppBundle\Service;

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Psr\Log\LoggerInterface;

class EmailQueueProcessor
{
    private $entityManager;
    private $mailer;
    private $logger;

    public function __construct(EntityManagerInterface $em, MailerInterface $mailer, LoggerInterface $logger)
    {
        $this->entityManager = $em;
        $this->mailer = $mailer;
        $this->logger = $logger;
    }

    // 处理指定数量的邮件批次
    public function processBatch(int $batchSize = 10): int
    {
        // 按创建时间排序,取出待发送的邮件
        $queueItems = $this->entityManager->getRepository('AppBundle:EmailQueue')
            ->findBy(['status' => 'pending'], ['createdAt' => 'ASC'], $batchSize);

        $sentCount = 0;
        foreach ($queueItems as $item) {
            try {
                // 构建邮件
                $email = (new Email())
                    ->from('your@domain.com')
                    ->to($item->getRecipient())
                    ->subject($item->getSubject())
                    ->html($item->getContent());

                // 发送邮件
                $this->mailer->send($email);

                // 更新状态为已发送
                $item->setStatus('sent');
                $item->setSentAt(new \DateTime());
                $sentCount++;
            } catch (\Exception $e) {
                // 发送失败标记为failed,后续可重试
                $item->setStatus('failed');
                $this->logger->error("邮件发送失败(收件人:{$item->getRecipient()}):" . $e->getMessage());
            }
        }

        // 批量保存更新
        $this->entityManager->flush();

        return $sentCount;
    }

    // 获取待发送邮件总数
    public function getPendingCount(): int
    {
        return $this->entityManager->getRepository('AppBundle:EmailQueue')
            ->count(['status' => 'pending']);
    }
}

记得在app/config/services.yml里配置这个服务:

services:
    AppBundle\Service\EmailQueueProcessor:
        arguments:
            - '@doctrine.orm.entity_manager'
            - '@mailer'
            - '@logger'

3. 创建AJAX接口(控制器)

写一个控制器方法,接收AJAX请求,处理一批邮件并返回进度数据:

// src/AppBundle/Controller/EmailQueueController.php
namespace AppBundle\Controller;

use AppBundle\Service\EmailQueueProcessor;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;

class EmailQueueController extends Controller
{
    /**
     * @Route("/email-queue/process-batch", name="email_queue_process_batch")
     */
    public function processBatchAction(EmailQueueProcessor $processor)
    {
        // 每次处理10封,可根据服务器性能调整数量
        $sentCount = $processor->processBatch(10);
        $pendingCount = $processor->getPendingCount();

        return new JsonResponse([
            'sent' => $sentCount,
            'pending' => $pendingCount,
            'totalProcessed' => $sentCount + ($pendingCount ? 0 : 0)
        ]);
    }
}

4. 前端AJAX实现进度更新

在页面里用JavaScript循环调用接口,实时更新进度条或计数器:

<div class="email-progress-container">
    <h3>邮件发送进度</h3>
    <p>已发送:<span id="sent-count">0</span></p>
    <p>待发送:<span id="pending-count">0</span></p>
    <div class="progress-bar-wrap">
        <div id="progress-bar" style="width: 0%; height: 20px; background: #28a745; transition: width 0.3s;"></div>
    </div>
    <button id="start-send" class="btn btn-primary">开始发送</button>
</div>

<script>
let totalSent = 0;
let isProcessing = false;
const startBtn = document.getElementById('start-send');

startBtn.addEventListener('click', () => {
    if (isProcessing) return;
    isProcessing = true;
    startBtn.disabled = true;
    processNextBatch();
});

function processNextBatch() {
    fetch('/email-queue/process-batch')
        .then(res => res.json())
        .then(data => {
            totalSent += data.sent;
            const pending = data.pending;

            // 更新UI
            document.getElementById('sent-count').textContent = totalSent;
            document.getElementById('pending-count').textContent = pending;
            
            const total = totalSent + pending;
            if (total > 0) {
                const progressPercent = (totalSent / total) * 100;
                document.getElementById('progress-bar').style.width = progressPercent + '%';
            }

            // 还有待发送的就继续调用
            if (pending > 0) {
                setTimeout(processNextBatch, 600); // 间隔0.6秒,避免频繁请求
            } else {
                isProcessing = false;
                startBtn.disabled = false;
                alert('所有邮件发送完成!');
            }
        })
        .catch(err => {
            console.error('处理失败:', err);
            isProcessing = false;
            startBtn.disabled = false;
            alert('发送过程中出现错误,请重试');
        });
}
</script>

关键注意事项

  • 批次大小调整:根据服务器的max_execution_time(PHP执行时间限制)调整批次数量,确保每个批次的处理时间在超时前完成(比如超时是30秒,就把批次控制在20秒内)。
  • 失败重试:对于标记为failed的邮件,可以做一个手动重试按钮,或者用Cron配合Symfony命令定时自动重试。
  • 事务控制:如果需要确保邮件发送和状态更新的原子性,可以在processBatch里开启Doctrine事务,但要注意事务过长可能导致锁表。
  • 日志监控:利用Monolog记录所有邮件发送的成功/失败日志,方便后续排查问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:00:11