PHP生成PDF发票遇内存耗尽问题,求优化方案及替代项目
优化PHP发票生成的内存占用与性能问题,附替代项目推荐
内存溢出问题的核心原因与优化方案
你遇到的内存溢出错误,核心原因大概率是未优化的Logo图片导致FPDF加载时占用过多内存,其次是PHP默认内存限制偏低。以下是针对性优化方案:
1. 压缩优化Logo图片
FPDF会将整张图片加载到内存中处理,若Logo原始尺寸过大(比如数千像素宽),会直接耗尽内存:
- 使用图片编辑工具或在线压缩工具,将Logo调整到发票页眉所需的实际大小(建议宽度不超过200px),同时压缩文件体积;
- 若不需要透明背景,将PNG格式转为JPEG,可大幅减小文件大小和内存占用。
2. 合理设置PHP内存限制
避免直接使用ini_set('memory_limit', '-1')(无限制占用内存),改为设置合理阈值:
ini_set('memory_limit', '256M'); // 根据实际需求调整,一般256M足够处理大多数发票场景
3. 代码层面的内存优化
- 提前保存关键变量并清理无用数据:在生成PDF前,释放不再需要的大对象/数组,避免内存占用:
// 提前保存后续需要的变量 $userId = $user->UserId; $factureId = $this->FactureId; // 清理无用变量 unset($user, $this->LignesFacture); gc_collect_cycles(); // 强制触发垃圾回收 - 简化循环逻辑:检查
var_check函数是否高效,避免在循环内重复执行冗余操作;若发票条目极多,确认是否需要分页处理(发票通常条目数量有限)。
4. 底层PDF库优化
artkonekt/pdf-invoice基于FPDF,而FPDF的内存管理相对老旧:
- 升级该项目到最新版本,查看是否有内存优化的更新;
- 替换底层依赖为TCPDF(FPDF的增强版,优化了内存使用),部分API兼容,迁移成本较低。
推荐的替代GitHub发票生成项目
如果artkonekt/pdf-invoice的性能仍无法满足需求,可考虑以下项目:
- mpdf/mpdf:功能全面的PDF生成库,支持HTML/CSS、多种图像格式,内存管理更高效,适合复杂文档生成;
- dompdf/dompdf:通过HTML/CSS渲染PDF,无需学习专用API,用网页样式即可定制发票,易于维护和扩展;
- tecnickcom/tcpdf:FPDF的增强分支,优化了内存占用和渲染速度,支持更多高级功能(如条形码、加密);
- barryvdh/laravel-dompdf:针对Laravel框架的dompdf封装,集成简单,适合Laravel项目快速开发发票功能。
优化后的发票生成代码示例
/* Header settings */ // 确保Logo已预先压缩到合适尺寸 $invoice->setLogo(__DIR__ . "/../assets/img/logo-couleur.png"); //logo image path $invoice->setColor("#ff3b44"); // pdf color scheme $invoice->setType("Facture"); // Invoice Type $invoice->setNumberFormat(",", " ", "right", true, false); $invoice->setReference($this->FactureId); // Reference $invoice->setDate(date('d/m/Y', strtotime($this->Date))); //Billing Date $invoice->setFrom(array("CORP1", "38 Blv Carnot, ", "Bureau 3", "Lille , 59000")); //setting client information if (var_check($user->BillingAdress)) { if (var_check($user->BillingAdress->Ligne2)) { $invoice->setTo( array( $user->BillingAdress->Denomination, $user->BillingAdress->Ligne1, $user->BillingAdress->Ligne2, $user->BillingAdress->Ville . ', ' . $user->BillingAdress->CodePostal, "Code client : " . $user->UserId ) ); } else { $invoice->setTo( array( $user->BillingAdress->Denomination, $user->BillingAdress->Ligne1, $user->BillingAdress->Ville . ', ' . $user->BillingAdress->CodePostal, "Code client : " . $user->UserId ) ); } } else { if (var_check($user->ShippingAdress->Ligne2)) { $invoice->setTo( array( $user->NameDisplayed, $user->ShippingAdress->Ligne1, $user->ShippingAdress->Ligne2, $user->ShippingAdress->Ville . ', ' . $user->ShippingAdress->CodePostal, "Code client : " . $user->UserId ) ); } else { $invoice->setTo( array( $user->NameDisplayed, $user->ShippingAdress->Ligne1, $user->ShippingAdress->Ville . ', ' . $user->ShippingAdress->CodePostal, "Code client : " . $user->UserId ) ); } } $invoice->flipflop(); //client information on the left $totalHT = 0; $totalTTC = 0; foreach ($this->LignesFacture as $ligne) { $totalHT = $totalHT + $ligne->PrixHT; $totalTTC = $totalTTC + $ligne->PrixTTC; $invoice->addItem($ligne->Description, (var_check($ligne->Detail) ? $ligne->Detail : false), false, false, $ligne->PrixHT/100, false, $ligne->PrixTTC/100); } $invoice->addTotal("Total HT", $totalHT/100); $invoice->addTotal("TVA 20%", ($totalTTC - $totalHT)/100); $invoice->addTotal("Total TTC", $totalTTC/100, true); if (var_check($payingDate)) { $invoice->addBadge("Payé le " . date('d/m/Y', strtotime($payingDate)), "#198754"); } $invoice->addParagraph("Numéro de commande : " . $this->CommandeId); $invoice->addParagraph("Je vous ai envoyé cette facture le " . date("d/m/Y", strtotime($this->Date)) . ". Celle-ci doit être réglée sous 30 jour(s) à compter de cette date. Passé ce délai, une pénalité de retard de 10 % sera appliquée, ainsi qu'une indemnité forfaitaire de 40€ due au titre des frais de recouvrement. Pas d'escompte pour règlement anticipé."); $invoice->setFooternote("CORP1 SAS | SIRET 91076923100036 | TVA FR65910769231"); $invoice->changeLanguageTerm('price', 'Prix HT'); $invoice->changeLanguageTerm('total', 'Prix TTC'); $invoice->changeLanguageTerm('number', 'Facture n°'); $invoice->changeLanguageTerm('date', 'Date'); $invoice->changeLanguageTerm('product', 'Désignation'); // 提前保存后续需要的变量并清理无用数据 $userId = $user->UserId; $factureId = $this->FactureId; unset($user, $this->LignesFacture); gc_collect_cycles(); $invoice->render(__DIR__ . '/../assets/factures/' . $userId . $factureId . '.pdf', 'F');
内容的提问来源于stack exchange,提问作者Victor Berson
相关产品推荐
相关产品推荐

