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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 07:54:50