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
相关产品推荐
相关产品推荐

