DEV Community

Cover image for PHP GC for FIles
Ahmed Raza Idrisi
Ahmed Raza Idrisi

Posted on

PHP GC for FIles

Let’s go step by step and see how PHP Garbage Collection & Memory Management behave when using classes, methods, and heavy tasks like PDF/Excel generation.


🏗️ 1. Classes & Methods

When you use classes and objects in PHP:

<?php
class User {
    public $name;
    function __construct($name) {
        $this->name = $name;
    }
}

function createUser() {
    $user = new User("Ahmed");
    echo $user->name;
} // <- $user goes out of scope here, memory freed

createUser();
?>
Enter fullscreen mode Exit fullscreen mode

✅ After the function ends, $user is destroyed and memory is released.
❌ But if there’s a circular reference inside objects, GC kicks in.


🌀 2. Circular References in Classes

<?php
class A {
    public $ref;
}
class B {
    public $ref;
}

$a = new A();
$b = new B();

$a->ref = $b;
$b->ref = $a; // <- circular reference

unset($a, $b); // memory NOT immediately freed
gc_collect_cycles(); // manually free cycles
?>
Enter fullscreen mode Exit fullscreen mode

➡️ Without GC, long-running scripts (like workers) would slowly eat up RAM.


📄 3. PDF Generation Example (Memory Heavy)

Suppose you’re using dompdf or TCPDF in Laravel/PHP.

<?php
use Dompdf\Dompdf;

function generatePDF($data) {
    $dompdf = new Dompdf();
    $dompdf->loadHtml("<h1>Hello $data</h1>");
    $dompdf->render();
    $pdf = $dompdf->output();

    file_put_contents("output.pdf", $pdf);

    // Free memory explicitly
    unset($dompdf, $pdf);
    gc_collect_cycles(); // cleanup
}

generatePDF("Ahmed");
?>
Enter fullscreen mode Exit fullscreen mode

⚠️ Problem: If you generate thousands of PDFs in a queue job without cleanup, memory keeps rising.
Solution: unset() variables, call gc_collect_cycles() occasionally.


📊 4. Excel Generation Example (Maatwebsite / PhpSpreadsheet)

<?php
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

function generateExcel() {
    $spreadsheet = new Spreadsheet();
    $sheet = $spreadsheet->getActiveSheet();
    $sheet->setCellValue('A1', 'Hello Ahmed');

    $writer = new Xlsx($spreadsheet);
    $writer->save('output.xlsx');

    // Free memory
    $spreadsheet->disconnectWorksheets();
    unset($spreadsheet, $writer);
    gc_collect_cycles();
}

generateExcel();
?>
Enter fullscreen mode Exit fullscreen mode

⚠️ If you create 100,000+ rows without cleaning up, memory will spike (hundreds of MBs).
✅ Best practice:

  • Use chunked writes (write in batches instead of loading everything in memory).
  • Always unset() objects and call gc_collect_cycles() in long-running scripts.

🚀 5. Real-World Tips (Laravel Jobs / Workers)

  • For short web requests: memory is freed at the end of the request anyway.
  • For long-running jobs (queues, daemons, APIs that loop over big datasets):

    • Always unset() large objects after use.
    • Call gc_collect_cycles() every few iterations.
    • For Excel, prefer ->store() with queue exports (Maatwebsite handles memory better).
    • For PDF, generate and store in chunks instead of bulk in one go.

Let’s clear the confusion — why do we need to unset() and call gc_collect_cycles() in PDF (or Excel) generation, even though variables go out of scope?


🧠 1. Normal Case – Variables Go Out of Scope

In a short script or a web request, PHP automatically:

  • Destroys variables when they go out of scope
  • Frees memory when the request ends

Example:

function test() {
    $a = "hello";  // allocated
} // <- function ends, $a destroyed, memory freed
Enter fullscreen mode Exit fullscreen mode

➡️ Nothing special needed.


🧾 2. Why PDF/Excel is Different

Libraries like Dompdf, TCPDF, PhpSpreadsheet are heavy:

  • They create big objects in memory (fonts, images, large buffers, XML tree for Excel, etc.)
  • Some of these objects reference each other (circular references)
  • PHP’s normal “out of scope” cleanup can’t immediately free all memory because circular references stay alive until GC runs

So, if you’re generating 1 PDF in a request, it doesn’t matter — memory is freed at request end.
But if you’re generating 100 PDFs in a loop (long-running worker), memory keeps growing until GC decides to run.


🔄 3. Example: PDF Without Explicit Cleanup

use Dompdf\Dompdf;

for ($i = 0; $i < 100; $i++) {
    $dompdf = new Dompdf();
    $dompdf->loadHtml("<h1>PDF $i</h1>");
    $dompdf->render();
    file_put_contents("pdf_$i.pdf", $dompdf->output());

    // no unset or GC
}
Enter fullscreen mode Exit fullscreen mode

⚠️ Problem: Memory grows with each loop because internal buffers & objects remain in circular references.
Eventually → script crashes with Out of Memory error.


✅ 4. With Explicit Cleanup

use Dompdf\Dompdf;

for ($i = 0; $i < 100; $i++) {
    $dompdf = new Dompdf();
    $dompdf->loadHtml("<h1>PDF $i</h1>");
    $dompdf->render();
    file_put_contents("pdf_$i.pdf", $dompdf->output());

    // Cleanup
    unset($dompdf);
    gc_collect_cycles(); // force GC to break cycles
}
Enter fullscreen mode Exit fullscreen mode

✔️ Memory stays stable, script runs fine.


⚡ 5. Key Difference

  • Short script/web request → memory is freed when request ends (no worries).
  • Long-running jobs (queue workers, daemons, batch processing) → memory accumulates unless you explicitly free heavy objects.

📝 Rule of Thumb

  • For normal requests: don’t bother, PHP cleans up.
  • For batch jobs / loops / workers: always unset() heavy objects and occasionally call gc_collect_cycles().

👉 That’s why for PDF/Excel generation in bulk, we explicitly free memory — not because PHP won’t free it at all, but because waiting until the script ends is too late for long-running processes.


In summary:

  • Classes & methods free memory normally.
  • Garbage Collection is important when objects reference each other (circular refs).
  • For PDF/Excel generation, explicitly free memory (unset, disconnectWorksheets, gc_collect_cycles) to prevent leaks.
  • In queues/long-running jobs, this is critical to avoid servers crashing from memory bloat.

Top comments (0)